36
votes

I am trying to test a controller of my ZF2 application. Suppose this controller is in my A module.

In the onBootstrap method of the Module.php of the module A I am using the service manager to retrieve a service of another module, say B, that I am not loading.

How can set a mock of the requested service in the service manager? Mind that I can not use $this->getApplicationServiceLocator() to do this in my test, since this is already calling the Module.onBootstrap method of my A module.

To post some code, this is what I am doing at the moment

bootstrap.php

namespace Application;

use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
use RuntimeException;

class Bootstrap
{
    protected static $serviceManager;

    public static function init()
    {
        $modulePath = static::findParentPath('module');
        $vendorPath = static::findParentPath('vendor');

        if (is_readable($vendorPath . '/autoload.php')) {
            $loader = include $vendorPath . '/autoload.php';
        } else {
            throw new RuntimeException('Cannot locate autoload.php');
        }

        $config = [
            'modules' => [
                'Application',
            ],
            'module_listener_options' => [
                'module_paths' => [
                    $modulePath,
                    $vendorPath
                ]
            ]
        ];

        $serviceManager = new ServiceManager(new ServiceManagerConfig());
        $serviceManager->setService('ApplicationConfig', $config);
        $serviceManager->get('ModuleManager')->loadModules();
        static::$serviceManager = $serviceManager;
    }

    protected static function findParentPath($path)
    {
        $dir = __DIR__;
        $previousDir = '.';
        while (!is_dir($dir . '/' . $path)) {
            $dir = dirname($dir);
            if ($previousDir === $dir) {
                return false;
            }
            $previousDir = $dir;
        }
        return $dir . '/' . $path;
    }

    public static function getServiceManager()
    {
        return static::$serviceManager;
    }
}

Bootstrap::init();

my actual test class

namespace Application\Functional;

use Application\Bootstrap;

use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;

class ValidateCustomerRegistrationTest extends AbstractHttpControllerTestCase
{
    public function setUp()
    {
        $serviceManager = Bootstrap::getServiceManager();
        $applicationConfig = $serviceManager->get('ApplicationConfig');

        $this->setApplicationConfig($applicationConfig);
        parent::setUp();
    }

    public function testRegisterValidUserWithOnlyEquomobiliData()
    {
        $this->getApplicationServiceLocator();
    }
}

Module.php simplified

namespace Application

Class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $serviceManager = $e->getApplication()->getServiceManager();
        $service = $serviceManager->get('Service\From\Other\Module');
    }
}
3
What are you trying to test? Generally, if you have trouble mocking objects it's a sign of bad architecture. Do you really need Module to depend on that service? Could it be injected somewhere else?chaos0815
Can you share the full class definition of the controller that you are testing?Wilt

3 Answers

1
votes

There is not enough data here to help you directly. It would be more useful to have the function ValidateCustomerRegistrationTest->getApplicationServiceLocator() for starters.

I hope I can help you indirectly.

Refactoring could be helpful

When I am writing a unit test I start with a few personal rules.

Only test the code being testing. Mock EVERYTHING else. No need to test something that should have it's own tests already.

How a function works should not be important. Only the input/output. This keeps your test viable even when the core of a function changes drastically.

/**
 * @param MvcEventInterface $event
 * @param Service\From\Other\ModuleInterface $service
 *
 * @return boolean
 */
public function onBootstrap(MvcEventInterface $event, Service\From\Other\ModuleInterface $service)
{
  return true;
}

Then in the test class:

public function testOnBootstrap(){
   $eventMock = $this->getMock(MvcEventInterface::class);
   $serviceMock = $this->getMock(ModuleInterface::class);
   $module = new Module();

   $result = $module->onBootstrap($eventMock, $serviceMock);
   $this->assertTrue($result);
}

* I clearly do not know what you are trying to test

When refactoring is not an option

There are two mock types that can help out, that occur to me right off the bat, mock, and mockBuilder. Take a look at the PHPUnit documentation for Test Doubles.

$serviceMock = $this->getMockBuilder(ServiceManager::class)
    ->disableOriginalConstructor()
    ->setMethods([
        '__construct',
        'get'
    ])
    ->getMock();

$serviceMock
    ->expects($this->any())
    ->method('get')
    ->will($this->returnValue(
        $this->getMock(ModuleManagerInterface::class)
    ));

Good luck, I hope you can let us know if we can help you more specifically. I would also recommend looking into SOLID Principles of Object Oriented Programing. One of many programming principles that should make your code clean, easy to extend, and easy to test.

1
votes

One way to accomplish what you're trying to do may be to force an overwrite of the service in the Service Manager before dispatching the request to your test controller. Here's an example of how to do that (NOTE: since what you're doing is during the module's bootstrap process, the example may not translate 100% to your situation).

Like other people mentioned: you may still want to double-check whether you can refactor to a cleaner approach, but that's another story.

1
votes

In order to mock the service manager and the calls made with it you can use mockery https://github.com/mockery/mockery. This library is framework agnostic so even if you use PHPUnit or an other tool it should work.

The bad way to use it but it should solve your problem fast is to overload the class using mockery('overload:myclass') where myclass is the instance of the service manager.

The best way to use the library is to use the depedency injection pattern to inject a mock of the service locator when your app is boostraping for the tests.

An easy way to go with it is to create a factory for your controller, inject the service locator inside. Then in your test you init the controller with the mock of the service locator as a parameter.