Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a method from the class you are testing with Prophecy?

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?

like image 850
alexandra Avatar asked Jan 21 '15 10:01

alexandra


2 Answers

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);
like image 79
Bang Avatar answered Oct 22 '22 16:10

Bang


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.

like image 43
Alexander Guz Avatar answered Oct 22 '22 16:10

Alexander Guz