7

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?

Wouter J
  • 41,455
  • 15
  • 107
  • 112
alexandra
  • 1,182
  • 13
  • 24
  • I've never used it, so can't myself help. However if you are still stuck in a couple of days (i.e. when a question bounty can be applied), ping me with @halfer and I'll add one. Good question! – halfer Jan 21 '15 at 14:50
  • Best answer I've found so far is that your class probably does not follow the S.O.L.I.D principles and does not have a single responsibility. So get your class straight before you try to use prophecy. – alexandra Jan 22 '15 at 15:55

3 Answers3

1

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);
Bang
  • 919
  • 4
  • 11
1

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.

Alexander Guz
  • 1,334
  • 12
  • 31
0

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

http://everzet.com/post/72910908762/conceptual-difference-between-mockery-and-prophecy

Ajant
  • 132
  • 4
  • 13