WitteStier
WitteStier

Reputation: 411

Test Doctrine flushed entity

I want to unittest my service class which saves 2 entities. Only the test fails because the person entity do not get an ID from the entityManager because it is mocked.

Is there a way to update the person object after flush is called for the first time.

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('[email protected]');

        $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('[email protected]');


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

        $user = array(
            'person_id' => 3,
            'email'     => '[email protected]',
        );

        $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();

    }
}

Upvotes: 3

Views: 3382

Answers (2)

Matteo
Matteo

Reputation: 39460

You can use the returnCallback function to act on the passed object (Some example here).

Practically you can tell to the persist mocked method to do some stuff on the object, as example, a working solutions based on your example:

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('[email protected]');

        $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();

    }

Tested with PHPUnit 4.3.5, for some tips check this SO Question

Hope this help

Upvotes: 5

Benjamin
Benjamin

Reputation: 1231

A couple points, main question first: if you are testing class Foo then all other complex dependencies should be mocked as this allows you to test the unit in isolation hence 'unit' testing.

If you create a factory class to create a Person from parameters, and pass this as a dependency to class Foo in your main application, then in your test suite you can pass in a Mock of this factory that will give you a Person with a pre initialized Id. Better still, if you want to avoid erroneous test results from the fact that Person will already have an Id before being flushed, is make the mock factory return a mock Person. You can then stub the mock's getId method with a callback that will only return non-null when the mock of EntityManager's flush method has been called.

Aside from this, I would recommend that you change your model slightly: if a Person is always required for a User, then require it as a Type-hinted parameter to the constructor, along with any other initially required dependencies. This way, you can abstract the relation between User and Person with an association mapping or directly by Id but your Foo class doesn't need to know which

Upvotes: 1

Related Questions