Welcome to Abdul Malik Ikhsan's Blog

Testing Zend Framework 2 factory using Prophecy

Posted in testing, 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: ,