ZF2 - Mocking service requested in Module.php

2022-08-30 13:12:06

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

In the method of the of the module I am using the service manager to retrieve a service of another module, say , that I am not loading.onBootstrapModule.phpAB

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

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');
    }
}

答案 1

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

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.


答案 2

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.


推荐