Beginners Guide to Magento 2 Custom Module Unit Tests

A blog about PHP unit testing may sound as dry as a bull’s arse going up a hill backwards, so I have done my best to keep this as simple and productive as possible.

I have written this blog post to help those who are new at writing unit tests get familiar with PHPUnit and how to write basic unit tests for your Magento 2 custom modules. Magento 2 extensions should be unit tested as a standard, though it certainly takes some getting use to if you’re a beginner. This tutorial also assumes you know how to write a basic Magento 2 module.

Magento 2 comes pre-installed with PHPUnit, an automated testing framework for PHP that has been included as a dependancy in Magento 2’s composer.json

Getting Started

I’m not going to go through all the basics in this tutorial because PHPUnit is a big ‘ol beast. Instead I recommend reading and having a go at the practical examples found in this excellent tutorial: PHPUnit Tutorial – Once you have your head around this, come back and read below as I’ll describe how this works with Magento 2.

Module Setup

Modules can (and should) be installed with Composer, and your tests should reside within a Test/Unit directory on your module’s root. Each test class must be suffixed with Test.php and should then mirror the directory path of the main class, for example:

Creare/Example/Observer/SetRobots.php
Creare/Example/Test/Unit/Observer/SetRobotsTest.php

Creare/Example/Observer/SetRobots.php

I have built a very basic module with an observer that checks a configuration setting and sets an alternative value for meta robots if true. The class implements the observer interface and I have injected a helper class which makes a call to the config:

<?php

namespace Creare\Example\Observer;

use Magento\Framework\Event\ObserverInterface;

class SetRobots implements ObserverInterface
{
    protected $_creareHelper;
    protected $_config;

    public function __construct(
        \Creare\Example\Helper\Data $_creareHelper,
        \Magento\Framework\View\Page\Config $_config
    )
    {
        $this->_creareHelper = $_creareHelper;
        $this->_config = $_config;
    }

    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        if ($this->_creareHelper->getConfig('setrobots'))
        {
            $this->_config->setRobots('NOINDEX, FOLLOW');
            return true;
        }
        return false;
    }
}

Creare/Example/Test/Unit/Observer/SetRobotsTest.php

I have created a mock of my helper which will always return ‘true’ when getConfig is called, and a mock of the observer class ‘SetRobots’. My observer will return true if the config setting has been enabled, and false if it has not been enabled.

<?php

namespace Creare\Example\Test\Unit\Observer;
use Creare\Example\Observer\SetRobots;

class SetRobotsTest extends \PHPUnit_Framework_TestCase
{
    protected $observer;

    public function setUp()
    {

        $this->creareHelper = $this->getMockBuilder('Creare\Example\Helper\Data')
            ->disableOriginalConstructor()
            ->getMock();

        $this->config = $this->getMockBuilder('Magento\Framework\View\Page\Config')
            ->disableOriginalConstructor()
            ->getMock();

        $this->observer = new SetRobots(
            $this->creareHelper,
            $this->config
        );

        $this->observerMock = $this->getMock('\Magento\Framework\Event\Observer', [], [], '', false);
    }

    public function testRobotsReturnsTrueWhenConfigIsTrue()
    {
        $this->creareHelper->expects($this->once())
            ->method('getConfig')
            ->will($this->returnValue(true));

        $observerReturnValue = $this->observer->execute($this->observerMock);
        $this->assertEquals($observerReturnValue, true);
    }
}

This is obviously a very basic example of a unit test, for demonstration purposes.

phpunit.xml

Once you have a module with a test written you can actually start performing some unit tests! You may be overwhelmed with excitement so I’ll let you take a second to let that sink in…. The PHPUnit configuration is stored in {ROOT}/dev/tests/unit/phpunit.xml.dist

Rename this file to phpunit.xml – now it’s time to configure which directories you want your unit tests to be ran from. By default it will check every core module, including the framework and all composer classes. In this tutorial I have installed my module with Composer so it is located in the /vendor directory. I have commented out the rest of the framework so my tests forever to complete.

<testsuite name="Magento Unit Tests">
        <directory suffix="Test.php">../../../vendor/creare/example/Test/Unit</directory>
        <!--<directory suffix="Test.php">../../../app/code/*/*/Test/Unit</directory>
        <directory suffix="Test.php">../../../dev/tools/*/*/Test/Unit</directory>
        <directory suffix="Test.php">../../../dev/tools/*/*/*/Test/Unit</directory>
        <directory suffix="Test.php">../../../lib/internal/*/*/Test/Unit</directory>
        <directory suffix="Test.php">../../../lib/internal/*/*/*/Test/Unit</directory>
        <directory suffix="Test.php">../../../setup/src/*/*/Test/Unit</directory>
        <directory suffix="Test.php">../../../update/app/code/*/*/Test/Unit</directory>
        <directory suffix="Test.php">../../../vendor/*/module-*/Test/Unit</directory>
        <directory suffix="Test.php">../../../vendor/*/framework/Test/Unit</directory>
        <directory suffix="Test.php">../../../vendor/*/framework/*/Test/Unit</directory>-->
    </testsuite>

If your module is developed directly into app/code then your directory path would be:

<directory suffix="Test.php">../../../app/code/Creare/Example/Test/Unit</directory>

To run our test we simply need to navigate to to the directory that contains phpunit.xml and run PHPUnit:

cd dev/tests/unit
php ../../../vendor/phpunit/phpunit/phpunit

There’s a host of options you can use with PHPUnit to produce coverage reports, debug and manage your unit testing that you can learn about by using the official documentation: https://phpunit.de/manual/current/en/textui.html