【问题标题】:Test Doctrine flushed entity测试学说刷新实体
【发布时间】:2015-10-27 03:58:57
【问题描述】:

我想对保存 2 个实体的服务类进行单元测试。 只有测试失败,因为人员实体没有从 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();

    }
}

【问题讨论】:

    标签: php doctrine-orm phpunit


    【解决方案1】:

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

    实际上,您可以告诉持久模拟方法对对象执行一些操作,例如,基于您的示例的工作解决方案:

    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

    希望有帮助

    【讨论】:

    • 感谢您的回答。我之前尝试过类似的东西,但没有成功。当我再次在家时,我会测试你的例子。
    • 嗨@WitteStier,不客气!如果您需要更多帮助,请告诉我
    • 我只能在没有使用 with 断言的情况下进行测试,因为 person 模型在测试之前已经更新。 $emMock ->expects($this->once()) ->method('persist') ->will($this->returnCallback(function($o) { if ($o instanceof $person) { $-> setId(4); } }))
    【解决方案2】:

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

    如果您创建一个工厂类来从参数创建一个Person,并将其作为依赖项传递给您的主应用程序中的类Foo,那么在您的测试套件中,您可以传入这个工厂的一个Mock,它将给你一个带有预初始化 ID 的 Person。更好的是,如果您想避免由于Person 在被刷新之前已经有一个 Id 的事实而导致错误的测试结果,请让模拟工厂返回一个模拟 Person。然后,您可以使用回调对 mock 的 getId 方法进行存根,该回调仅在调用 EntityManager 的 flush 方法的 mock 时返回非 null。

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

    【讨论】:

    • 感谢您的回答。这只是我的问题的一个简单示例。我已经嘲笑了模型之外的所有依赖项。 Person 和 User 只是举例。我不想将模型添加到工厂,因为模型很多,这会弄乱我的配置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多