Welcome to Abdul Malik Ikhsan's Blog

Testing “expects($this->any())” with Prophecy with Spying

Posted in Tutorial PHP by samsonasik on September 27, 2015

Testing method call from Collaborator that may be 0 or any other count with phpunit Framework test case can be done with expects($this->any()) from mock object. If we are using Prophecy for mocking tools, it can be done with spying.
However, the spying itself need checks whenever method is called or not. We need findProphecyMethodCalls() call againsts ObjectProphecy for that.

For example, we have a class with collaborator like the following:

namespace App;

class Awesome
{
    private $awesomeDependency;

    public function __construct(AwesomeDependency $awesomeDependency)
    {
        $this->awesomeDependency = $awesomeDependency;
    }

    public function process($data)
    {
        $rand = rand(1, 2);
        if ($rand === 1) {
            return $this->awesomeDependency->process($data);
        }
        return $data;
    }
}

The rand() usage is just a sample, in real app, we may have a heavy logic and it may fall to not call the collaborator.

The tests can be done like this:

namespace AppTest;

use App\Awesome;
use App\AwesomeDependency;
use PHPUnit_Framework_TestCase;
use Prophecy\Argument;
use Prophecy\Argument\ArgumentsWildcard;

class AwesomeTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        $this->awesomeDependency = $this->prophesize(AwesomeDependency::class);
        $this->awesome     = new Awesome($this->awesomeDependency->reveal());
    }

    public function testProcess()
    {
        $data = [
            'foo' => 'abcdef',
        ];

        // make \Prophecy\MethodProphecy instance
        $methodProphecy = $this->awesomeDependency
                               ->process(Argument::exact($data));
        
        // call method from actual instance
        $this->awesome->process($data);

        $calls = $this->awesomeDependency->findProphecyMethodCalls(
            'process',
            new ArgumentsWildcard([$data])
        );
        $count = count($calls);
        
        // assert how many times it called 
        $methodProphecy->shouldBeCalledTimes($count);
        if ($count) {
            // echoing just to prove
            echo 'Method from collaborator has been called';
            $methodProphecy->shouldHaveBeenCalled();
        } else {
            // echoing just to prove
            echo 'Method from collaborator has not been called';
            $methodProphecy->shouldNotHaveBeenCalled();
        }
    }
}

Of course, it may be can’t be called ‘expecting’ before for something has done, it may be can be called as ‘recording’ what already happen, but by this usage, we can prove if it actually called in actual code.

Advertisements

Testing Zend Framework 2 factory using Prophecy

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on August 29, 2015

Simulate every detail what classes methods doing with phpunit sometime make you little crazy and make you do too many effort ? There is a library to reduce that! It named Prophecy, a “Highly opinionated mocking framework for PHP 5.3+”.

In this post, I want to show you in Zend Framework 2 application case, which if we are using ZF2 ~2.4.0, we already have zendframework/zend-test which require phpunit/phpunit:~4.0. The Prophecy is included in phpunit start from 4.5, so the requirement already met. If we use old ZF2 version, we can add "phpspec/prophecy-phpunit": "~1.5" under require-dev in our composer requirement.

For example, we have a factory class for controller creation like the following:

namespace MyModule\Factory\Controller;

use MyModule\Controller\MyController;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;

class MyControllerFactory implements FactoryInterface
{
    /**
     * @{inheritDoc}
     */
    public function createService(ServiceLocatorInterface $sl)
    {
        $services           = $sl->getServiceLocator();
        $formElementManager = $services->get('FormElementManager');
        $myForm             = $formElementManager->get('MyModule\Form\MyForm');
        
        $controller = new MyController($myForm);

        return $controller;
    }
}

Without Prophecy, we need write the tests like the following:

namespace MyModule\Factory\Controller;

use PHPUnit_Framework_TestCase;
use MyModule\Factory\Controller\MyControllerFactory;

/**
 * @author Abdul Malik Ikhsan <samsonasik@gmail.com>
 */
class MyControllerFactoryTest extends PHPUnit_Framework_TestCase
{
    /** @var MyControllerFactory */
    protected $factory;

    /** @var ServiceLocatorInterface */
    protected $serviceLocator;

    /** @var \Zend\Mvc\Controller\ControllerManager */
    protected $controllerManager;

    public function setUp()
    {
        $this->controllerManager = $this->getMockBuilder('Zend\Mvc\Controller\ControllerManager') 
                                        ->disableOriginalConstructor()
                                        ->getMock();
        $this->serviceLocator    = $this->getMock('Zend\ServiceManager\ServiceLocatorInterface');

        $factory = new MyControllerFactory();
        $this->factory = $factory;
    }

    public function testCreateService()
    {
        $formElementManager = $this->getMockBuilder('Zend\Form\FormElementManager')
                                   ->disableOriginalConstructor()
                                   ->getMock();
        $myForm  = $this->getMockBuilder('MyModule\Form\MyForm')
                                   ->disableOriginalConstructor()
                                   ->getMock();
        $formElementManager->expects($this->once())
                           ->method('get')
                           ->willReturn($myForm);
        $this->serviceLocator->expects($this->once())
                             ->method('get')
                             ->with('FormElementManager')
                             ->willReturn($FormElementManager);
        
        $this->controllerManager->expects($this->once())
                                ->method('getServiceLocator')
                                ->willReturn($this->serviceLocator);

        $result = $this->factory->createService($this->controllerManager);
        $this->assertInstanceOf('MyModule\Controller\MyControllerFactory', $result);
    }
}

This is the example for 1 dependency, we can to call expects($this->once()), what if we have many dependencies which use many services?, we can end up doing something like this:

$myService1 = $this->getMockBuilder('MyModule\Service\MyService1')
                   ->disableOriginalConstructor()
                   ->getMock();
$this->serviceLocator->expects($this->at(0))
                     ->method('get')
                     ->with('MyService1')
                     ->willReturn($myService1);
$myService2 = $this->getMockBuilder('MyModule\Service\MyService2')
                   ->disableOriginalConstructor()
                   ->getMock();
$this->serviceLocator->expects($this->at(1))
                  ->method('get')
                  ->with('MyService2')
                  ->willReturn($myService2);

And if we added a service dependency in the middle, we need to reset the increment.

With Prophecy, it now simplified, we can eliminate them, and use the mock just like the object do, like the following:

$formElementManager = $this->prophesize('Zend\Form\FormElementManager');
$myForm             = $this->prophesize('MyModule\Form\MyForm');
$formElementManager->get('MyModule\Form\MyForm')->willReturn($myForm);

Let’s see the full code for testing factory above if we are using Prophecy:

namespace MyModule\Factory\Controller;

use PHPUnit_Framework_TestCase;
use MyModule\Factory\Controller\MyControllerFactory;

/**
 * @author Abdul Malik Ikhsan <samsonasik@gmail.com>
 */
class MyControllerFactoryTest extends PHPUnit_Framework_TestCase
{
    /** @var MyControllerFactory */
    protected $factory;

    /** @var ServiceLocatorInterface */
    protected $serviceLocator;

    /** @var \Zend\Mvc\Controller\ControllerManager */
    protected $controllerManager;

    public function setUp()
    {
        $this->controllerManager = $this->prophesize('Zend\Mvc\Controller\ControllerManager');
        $this->serviceLocator    = $this->prophesize('Zend\ServiceManager\ServiceLocatorInterface');

        $factory = new MyControllerFactory();
        $this->factory = $factory;
    }

    public function testCreateService()
    {
        $formElementManager = $this->prophesize('Zend\Form\FormElementManager');
        $myForm             = $this->prophesize('MyModule\Form\MyForm');
        $formElementManager->get('MyModule\Form\MyForm')->willReturn($myForm);
        $this->serviceLocator->get('FormElementManager')->willReturn($formElementManager);
        $this->controllerManager->getServiceLocator()->willReturn($this->serviceLocator);

        $result = $this->factory->createService($this->controllerManager->reveal());
        $this->assertInstanceOf('MyModule\Controller\MyController', $result);
    }
}

That’s it ;). It now simplified.

Tagged with: ,