测试 Doctrine 实体

Test Doctrine flushed entity

我想对我的服务 class 进行单元测试,它可以节省 2 个实体。 只有测试失败,因为 person 实体没有从 entityManager 获得 ID,因为它被模拟了。

有没有办法在第一次调用 flush 后更新 person 对象。

class Foo
{

    ...

    public function save()
    {
        $em = $this->getEntityManager();

        $person = new Person();
        $person->setName('Dude');

        $em->persist($person);
        $em->flush();

        $user = new User();
        $user->setPersonId($person->getId());
        $user->setEmail('dude@example.com');

        $em->persist($user);
        $em->flush();
    }   
}

class FooTest
{

    ...

    public function testSave_UserIsSaved()
    {
        $person = new Person();
        $person->setName('dude');

        $user = new User();
        $user->setPersonId(4); // <-- this is where it gets wrong
        $user->setEmail('dude@example.com');


        $person = array(
            'name' => 'Dude',
        );

        $user = array(
            'person_id' => 3,
            'email'     => 'dude@example.com',
        );

        $emMock = $this->getMockBuilder('\Doctrine\ORM\EntityManager')
                       ->setMethods(array('persist', 'flush'))
                       ->getMock();

        $emMock->expects($this->exactly(2))
               ->method('persist')
               ->with(
                    $this->logicalOr(
                        $this->equalTo($person),
                        $this->equalTo($user)
                    )
               );

        $emMock->expects($this->exactly(2))
               ->method('flush');

        $foo = new Foo($emMock);
        $foo->save();

    }
}

几点,首先是主要问题:如果您正在测试 class Foo 那么所有其他复杂的依赖项都应该是 mocked 因为这允许您单独测试单元因此'unit' 测试。

如果您创建工厂 class 以从参数创建 Person,并将其作为依赖项传递给主应用程序中的 class Foo,则在你的测试套件你可以传入这个工厂的模拟,它会给你一个 Person 和一个预初始化的 Id。更好的是,如果你想避免错误的测试结果,因为 Person 在被刷新之前已经有一个 Id,将模拟工厂 return 变成模拟 Person。然后,您可以使用回调对模拟的 getId 方法进行存根,当调用 EntityManager 的 flush 方法的模拟时,该回调只会 return 非空。

除此之外,我建议您稍微更改模型:如果 User 始终需要 Person,则需要将其作为构造函数的类型提示参数,以及任何其他最初需要的依赖项。这样,您可以使用 association mapping 或直接通过 Id 来抽象 UserPerson 之间的关系,但 您的 Foo class 不会不需要知道哪个

您可以使用returnCallback函数作用于传递的对象(Some example here)。

实际上你可以告诉 persist 模拟方法在对象上做一些事情,例如,一个基于你的例子的工作解决方案:

class FooTest extends \PHPUnit_Framework_TestCase {

    public function testSave_UserIsSaved()
    {
        $person = new Person();
        $person->setName('Dude');

        $user = new User();
        $user->setPersonId(4); // <-- this is where it gets wrong
        $user->setEmail('dude@example.com');

        $emMock = $this->getMockBuilder('\Doctrine\ORM\EntityManager')
            ->setMethods(array('persist', 'flush'))
                ->disableOriginalConstructor()
            ->getMock()
        ;

        $emMock->expects($this->exactly(2))
            ->method('persist')
            ->with(
                $this->logicalOr(
                    $this->equalTo($person),
                    $this->equalTo($user)
                )
            )
            ->will($this->returnCallback(function($o) {
                if ($o instanceof \Acme\DemoBundle\Model\Person){
                    $o->setId(4);
                }
            }));



        $emMock->expects($this->exactly(2))
            ->method('flush');

        $foo = new Foo($emMock);
        $foo->save();

    }

已使用 PHPUnit 4.3.5 进行测试,一些提示请查看 this SO Question

希望对您有所帮助