Reputation: 1192
I want to use Prophecy ("phpspec/prophecy-phpunit") for the first time to create unit tests for my classes. I want to test a function that calls another function in the same service, here's the code:
class UserManager
{
private $em;
private $passwordHelper;
public function __construct(\Doctrine\ORM\EntityManager $em, \MainBundle\Helper\PasswordHelper $passwordHelper)
{
$this->em = $em;
$this->passwordHelper = $passwordHelper;
}
public function getUserForLdapLogin($ldapUser)
{
$dbUser = $this
->em
->getRepository('MainBundle:User')
->findOneBy(array('username' => $ldapUser->getUsername()));
return (!$dbUser) ?
$this->createUserFromLdap($ldapUser) :
$this->updateUserFromLdap($ldapUser, $dbUser);
}
First problem I had was that I was using findOneByUsername
and Prophecy, as far as my knowledge goes, does not allow you to: mock magic methods (_call
for EntityRepository
), mock methods that do not exist, mock the class you are testing. If these are true I'm in a bit of a pickle, meaning I cannot test this function without testing the other functions in the class.
So far, my test looks like this:
class UserManagerTest extends \Prophecy\PhpUnit\ProphecyTestCase
{
public function testGetUserForLdapLoginWithNoUser()
{
$ldapUser = new LdapUser();
$ldapUser->setUsername('username');
$em = $this->prophesize('Doctrine\ORM\EntityManager');
$passwordHelper = $this->prophesize('MainBundle\Helper\PasswordHelper');
$repository = $this->prophesize('Doctrine\ORM\EntityRepository');
$em->getRepository('MainBundle:User')->willReturn($repository);
$repository->findOneBy(array('username' => 'username'))->willReturn(null);
$em->getRepository('MainBundle:User')->shouldBeCalled();
$repository->findOneBy(array('username' => 'username'))->shouldBeCalled();
$service = $this->prophesize('MainBundle\Helper\UserManager')
->willBeConstructedWith(array($em->reveal(), $passwordHelper->reveal()));
$service->reveal();
$service->getUserForLdapLogin($ldapUser);
}
}
And of course, the tests fail because the promises on $em
, and the repository are not fulfilled. If I instantiate the class I am testing, the tests fail because the function then calls createUserFromLdap()
on the same class and that is not tested.
Any suggestions?
Upvotes: 8
Views: 7914
Reputation: 158
Regarding your problem of not being able to mock methods that do not exist, you could use
http://docs.mockery.io/en/latest/
in stead of prophecy. Mockery allows you to do just that. Strictly speaking, that does break some of the rules of good design, but on the other hand, sometimes it's just very useful. Anyways, mockery is very similar, as far as features go, and it's equally as intuitive and easy to use imo. However, they still haven't released stable version, so just be aware of that if you do decide to use it.
Here you can find a good comparison of two libraries:
Conceptual difference between Mockery and Prophecy
Upvotes: 0
Reputation: 1364
What you're trying to achieve is a partial mock, which is not supported by Prophecy. More about it here https://github.com/phpspec/prophecy/issues/101 and https://github.com/phpspec/prophecy/issues/61.
TL;DR; Design your classes with single responsibility in mind, so you don't have to mock other functionality.
Upvotes: 2
Reputation: 929
First problem :
Don't use magic, magic is evil. __call may lead to unpredictable behavior.
"the promises on $em, and the repository are not fulfilled" :
Don't make your code depend on Class but Interface. Then mock the Interface instead of Class ! You should mock ObjectManager instead of EntityManager. (don't forget to change the type of your parameters)
And the last point :
Before reveal.
$service->createUserFromLdap()
->shouldBeCalled()
->willReturn(null);
Upvotes: 1