mage2-phpunit

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

  • Manuele Menozzi

    There is an error at line 21 of SetRobotsTest. It should be “new SetRobots(” instead of “new SetSearchMetaRobots(“.

    • http://adammoss.co.uk Adam Moss

      Fixed, thanks!

  • Ivan Chepurnyi

    It is better to run your own unit tests within your composer package and with your own phpunit.xml.dist, do not add them to existing system.

    For integration tests, that might be a good idea to have kind of re-usable package, that will allow usage of Magento Framework fixtures mechanism, but that is a different story.

    • http://adammoss.co.uk Adam Moss

      Ok, thanks for the feedback.

    • Stefan Doorn

      How would you do that if depending on a lot of framework code? I can only imagine it’s possible to test the things that do not depend on M2 or as far as you can mock things. But I guess that’s tedious work. Do you have some examples?

  • Dusan Lukic

    Nice article, thanks. Although I think as there is no obvious need for the observer execute method to return anything, a better approach in my opinion would be to create a separate method in observer that would return the config value and then to unit test that method, or to test the state of the config object. Just an idea :)

  • Viraj

    Someone tell me the feasibility of automation testing in Magento 2 and if yes than how to achive?