How to instantiate ServiceManager for your tests in ZF2

If you follow ZF2 tutorial you’ll have this piece of code in Module.php

    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'Api\Model\BusinessRuleTable' => function($sm) {
                    $tableGateway = $sm->get('BusinessRuleTableGateway');
                    $table = new Model\BusinessRuleTable($tableGateway);
                    return $table;
                },
                'BusinessRuleTableGateway' => function($sm) {
                    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                    $resultSetPrototype = new ResultSet();
                    $resultSetPrototype->setArrayObjectPrototype(new Model\BusinessRule());
                    return new TableGateway('BusinessRules', $dbAdapter, null, $resultSetPrototype);
                }
            ),
        );
    }

So in case you’re wondering how to instantiate your ModelTable (in my case BusinessRuleTable), here’s my (crude) solution.

Why not use mocks? BusinessRuleTableGateway is not defined as class, but as callable factory. Great stuff for dependency injection, but a small pain in tests.


namespace ApiTest\Model;

use Api\Module;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
use Zend\Stdlib\ArrayUtils;

class BusinessRuleTableTest extends \PHPUnit_Framework_TestCase {

    public $serviceManager;

    public function setUp()
    {
        $module = new Module(); // this is your Module

        $moduleConfig = $module->getServiceConfig();

        /* 
        this below should probably be more flexible and use config's
        'config_glob_paths' => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
        */
        $globalConfig = require("config/autoload/global.php");
        $localConfig = require("config/autoload/local.php");
        $testConfig = require("config/autoload/test.local.php");

        // get array for Zend\Mvc\Service\ServiceManagerConfig
        $serviceManagerConfig = ArrayUtils::merge($moduleConfig, $globalConfig['service_manager']);

        //instantiate ServiceManager, which you'll use later for retrieving objects
        $configuration = new ServiceManagerConfig($serviceManagerConfig);
        $this->serviceManager = new ServiceManager($configuration);

        $allConfig = ArrayUtils::merge($globalConfig, $localConfig);
        $allConfig = ArrayUtils::merge($allConfig, array('service_manager' => $serviceManagerConfig));
        $allConfig = ArrayUtils::merge($allConfig, $testConfig);

        // set Config service, service manager can't operate without it
        $this->serviceManager->setService("Config", $allConfig);
    }

    public function test_instance()
    {
        // this is how you get instance
        $table = $this->serviceManager->get("Api\Model\BusinessRuleTable");
        $this->assertInstanceOf("Api\Model\BusinessRuleTable", $table);

    }

    public function test_fetchAllWithParams_should_call_getSelectForOptions()
    {
        /* If you need to mock ModelTable but call method which uses
           table gateway, you should get one from serviceManager ...*/
        $tableGateway = $this->serviceManager->get("BusinessRuleTableGateway");
        // ... and pass it as first param to constructor
        $table = $this->getMock("Api\Model\BusinessRuleTable", array("getSelectOptionsForParams"), array($tableGateway));
        $table->expects($this->once())
            ->method("getSelectOptionsForParams");

        // this method calls $this->tableGateway->select()
        $table->fetchAllWithParams();
    }

    public function test_getOptionsForParams_should_not_have_not_allowed_fields()
    {
        /* In case you don't need to call tableGateway, it's ok to just mock ModelTable */
        $table = $this->getMock("Api\Model\BusinessRuleTable", array("getAllowedCriteriaFields"), array(), "", false /* don't call original constructor! */);
        $table->expects($this->any())
            ->method("getAllowedCriteriaFields")
            ->will($this->returnValue(array("Field1")));

        $params = array("Field1" => "value1");

        $options = $table->getOptionsForParams($params);

        $this->assertArrayNotHasKey("Field2", $options);
    }
}

It’s rough around the edges, but can keep you going. To complete the example: in my ‘config/autoload/test.local.php’ I overwrite some DB details for tests:

return array(
    'db' => array(
        'username' => 'foo',
        'password' => 'shh!secret',
        'dsn'      => 'mysql:dbname=database_name_test;host=mysql.example.com',
    ),
);

Share Button

Leave a Reply

Your email address will not be published. Required fields are marked *