Reputation: 411
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
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
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