alexandra
alexandra

Reputation: 1192

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?

Upvotes: 8

Views: 7914

Answers (3)

Ajant
Ajant

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

Alexander Guz
Alexander Guz

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

Bang
Bang

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

Related Questions