Testing ZF2 module services

There's an important question often rising when working on Zend Framework 2 module: should I test service factories? After all, they are usually trivial, they create some object and inject it with dependencies from ServiceManager. Having one test per factory seems to be an overkill.

Better to go one step back, and ask yourself a question: what exactly do you want to test?

To better illustrate this problem, let's consider two services available from your module:

namespace MyModule\Service;

class FooService
{
    public function __construct()
    {
    }
}

class BarService
{
    /**
     * @var FooService
     */
    private $fooService;

    public function __construct(FooService $fooService)
    {
        $this->fooService = $fooService;
    }
}

FooService is a simple class with no dependencies, and it is required by BarService. We need to have a factory for BarService:

namespace MyModule\Factory;

use MyModule\Service\BarService;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class BarServiceFactory implements FactoryInterface
{
    /**
     * @inheritdoc
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        return new BarService(
            $serviceLocator->get('MyModule\Service\FooService')
        );
    }
}

This code is trivial, and FooService is type hinted in constructor, so one could argue that writing unit test for factory itself is pointless. What is an alternative?

Back to question: "what exactly do you want to test?". Typically, services in your module are exposed using configuration file, which should be treated as kind of module interface. Then, it is worth to test if your module really exposes this services, no matter how they are created or what dependencies do they have. Take a look at example configuration that provides two services defined above:

return [
    'service_manager' => [
        'invokables' => [
            'MyModule\Service\FooService' => 'MyModule\Service\FooService',
        ],
        'factories' => [
            'MyModule\Service\BarService' => 'MyModule\Factory\BarServiceFactory',
        ],
    ]
];

If you follow convention of having service name exactly the same as returned class name, your test can easily iterate over configuration, pull every service from ServiceManager, and validate it:

namespace MyModuleTest;

use MyModule\Module;
use PHPUnit_Framework_TestCase;

class ModuleTest extends PHPUnit_Framework_TestCase
{
    /**
     * Scans service manager configuration, returning all services created by factories and invokables
     * @return array
     */
    public function provideServiceList()
    {
        $config = include __DIR__ . '/../../config/module.config.php';
        $serviceConfig = array_merge(
            isset($config['service_manager']['factories'])?$config['service_manager']['factories']:array(),
            isset($config['service_manager']['invokables'])?$config['service_manager']['invokables']:array()
        );
        $services = array();
        foreach ($serviceConfig as $key => $val) {
            $services[] = array($key);
        }
        return $services;
    }

    /**
     * @dataProvider provideServiceList
     */
    public function testService($service)
    {
        $sm = Bootstrap::getServiceManager();
        // test if service is available in SM
        $this->assertTrue($sm->has($service));
        // test if correct instance is created
        $this->assertInstanceOf($service, $sm->get($service));
    }
}

This approach has several advantages:

  • you ensure that your module correctly exposes everything defined in configuration
  • this test will catch every typo or notice you may have in any factory
  • you get 100% test coverage over all factories
  • you don't waste time on writing useless tests

Note that you'll likely need to adjust above code, to match your module specifics. You can use it as a base to test controller plugins or view helpers as well.

About non-trivial factories

Just to make sure: I'm not against testing factories in every situation! When your factory does something more complex (like creating and injecting adapters based on configuration), you should obviously create separate test case.

Running tests

In order to run this code, you'll need a bootstrapper that sets up ZF2 application and your module. Official documentation has a chapter about unit testing - you can take sample code and alter it to match your needs:

<?php
namespace MyModuleTest;

use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Application;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
use RuntimeException;

error_reporting(E_ALL | E_STRICT);
chdir(__DIR__);

/**
 * Test bootstrap, for setting up autoloading
 */
class Bootstrap
{
    protected static $serviceManager;

    public static function init()
    {
        $zf2ModulePaths = array(dirname(dirname(__DIR__)));
        if (($path = static::findParentPath('vendor'))) {
            $zf2ModulePaths[] = $path;
        }
        if (($path = static::findParentPath('module')) !== $zf2ModulePaths[0]) {
            $zf2ModulePaths[] = $path;
        }

        static::initAutoloader();

        // use ModuleManager to load this module and it's dependencies
        if (file_exists(__DIR__ . '/TestConfiguration.php')) {
            $config = require __DIR__ . '/TestConfiguration.php';
        } else {
            $config = require __DIR__ . '/TestConfiguration.php.dist';
        }

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

        $application = new Application($config, $serviceManager);
        $application->bootstrap();

        static::$serviceManager = $serviceManager;
    }

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

    protected static function initAutoloader()
    {
        $vendorPath = static::findParentPath('vendor');

        $zf2Path = getenv('ZF2_PATH');
        if (!$zf2Path) {
            if (defined('ZF2_PATH')) {
                $zf2Path = ZF2_PATH;
            } elseif (is_dir($vendorPath . '/ZF2/library')) {
                $zf2Path = $vendorPath . '/ZF2/library';
            } elseif (is_dir($vendorPath . '/zendframework/zendframework/library')) {
                $zf2Path = $vendorPath . '/zendframework/zendframework/library';
            }
        }

        if (!$zf2Path) {
            throw new RuntimeException(
                'Unable to load ZF2. Run `php composer.phar install` or'
                . ' define a ZF2_PATH environment variable.'
            );
        }

        if (file_exists($vendorPath . '/autoload.php')) {
            include $vendorPath . '/autoload.php';
        }

        include $zf2Path . '/Zend/Loader/AutoloaderFactory.php';
        AutoloaderFactory::factory(array(
            'Zend\Loader\StandardAutoloader' => array(
                'autoregister_zf' => true,
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/' . __NAMESPACE__,
                ),
            ),
        ));
    }

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

Bootstrap::init();
Now prepare simple configuration file telling ZF to load your module (add dependencies if it has any):
<?php
return array(
    'modules' => array(
        'MyModule',
    ),
    'module_listener_options' => array(
        'config_glob_paths' => array(),
        'module_paths' => array(),
    ),
);

Finally, PHPUnit XML configuration (phpunit.xml):

<phpunit bootstrap="Bootstrap.php">
    <testsuite name="MyModuleTest">
        <directory>MyModuleTest</directory>
    </testsuite>
    <filter>
        <whitelist addUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">../src</directory>
        </whitelist>
        <blacklist>
            <directory>../vendor</directory>
        </blacklist>
    </filter>
</phpunit>

Github example

I prepared working demo that can be downloaded from Github. Go to example.

Read more 

MtMail: e-mail module for ZF2

I'm happy to present a ZF2 module that handles composing and sending e-mail messages.

Why another module? There are a few of them already available on ZF modules website. However, when I was looking for solution to use in my application, I quickly realized that most of them are either outdated, or they miss features I needed. That's why I decided to write my own.

My intention was to create something powerful, but still simple to use. You an customize e-mail headers, add layout, automatically generate plaintext version of HTML e-mail, and so on. But you can also start composing and sending e-emails from your controllers with just a few lines of code.

Read more 

Using standalone Zend\View

Zend\View is pretty advanced rendering engine, with multiple useful features. It is working nicely within ZF2's MVC stack, where it is automatically configured for you. But how to use it without full MVC?

This can be useful in some situations: when building Your Own Microframework™, when creating an application based on ZF2 components, or (in my case) when working on module that is supposed to render something outside MVC flow. All of this projects can benefit from nested templates, multiple rendering engines, or pluggable architecture of Zend\View.

So, how to do that?

Read more 

Extracting single table from huge MySQL dump

During last few weeks I had to work with relatively big MySQL dumps. I had to find interesting rows in about 400 files, each of them taking 40 minutes to import. In order to speed things up, I found simple tool that allowed me to extract only interesting tables.

The tool is actually single Perl script, named extract_sql.pl (available on Github). It allows extracting tables with simple command:

mat@server:~$ extract_sql.pl -t TABLE_NAME -r DUMP_FILE.sql

This command will print dump to console output, so you may want to redirect it to some file:

mat@server:~$ extract_sql.pl -t TABLE_NAME -r DUMP_FILE.sql > table_name.sql

Finally, extract_sql.pl is able to read input from stdin, so it is easy to extract and import single table from compressed dump file:

mat@server:~$ zcat DUMP_FILE.sql | extract_sql.pl -t TABLE_NAME \
| mysql dest_database -u username -p
Read more 

Automated MySQL backup on dedicated server or VPS

I just moved all my small projects to new dedicated server. I have to admit, until now I wasn't paying attention to regular backups. I simply ran mysqldump and copied everything to my laptop every few months. I didn't have any problems with that, as my data was not very critical. But, this time I decided to build something better - I wanted database backups to be generated automatically, at regular intervals.

I knew more or less what to do, I just had to put all pieces together. This tutorial shows necessary steps to build similar solution on your server.

Read more 

Learning ZF2: Application flow

Zend Framework 2

This post covers basic tasks that you may want to do within controller: forwarding to different actions, redirecting, and displaying 404 page. Once again, I will show how this tasks can be achieved in both ZF1 and ZF2.

Read more 

About me
Mateusz Tymek

After compiling my very first lines of code at the age of 12, I became passionate about computer science and technology. Now I'm a PHP developer, enjoying my work as a member of Cleeng team.
Doing some sports in my spare time.

Github activity