#RealMagento: Automated Testing in Magento 2

With this post, I aim to show you the basics of how to perform integration and unit testing in Magento 2 through a simple plugin module. This will automate more of your worfklow and grow your confidence in the code you write.

For these tests, we’ll be using the module that we built in this blog post.

A quick note: This article was written shortly after the release of Magento version 2.1.0. Take this into consideration if you notice some differences in file names or the file contents in the Magento version that you’re currently using.

I’m also leaving out the configuration of IDEs, such as PHPStorm, to run unit tests. I will assume that you’ve already set this up or are running the tests from the command line using the phpunit executable, located at /vendor/bin/phpunit, that composer pulls in via the “phpunit/phpunit”: “4.1.0” requirement in the root composer.json file.

Automated Testing in Magento 2

ecommerce, website design, demac media, magento 2, automated testing, magento ecommerce,magento 2 testing

What I will cover:

  • Writing basic integration and unit tests.
  • The Magento TestFramework test helper classes/methods.
  • Properly mocking your test dependencies with the PHPUnit mocking support.
  • Configuration files associated with integration/unit testing, where the example .dist files exist in the core code, and what values should be changed.

What I can’t cover:

  • How to write well-designed Magento 2 unit tests. This is much too large a topic. You learn better by writing a lot of tests and discovering for yourself what allows you to make those tests maintainable.
  • How to fully leverage the features of the PHPUnit testing framework. For this, I would again recommend writing lots of tests; not just in Magento 2 code, but also in your PHP development outside of Magento 2. The PHPUnit online documentation is excellent, so go give that a read.
    • Note: at the time of writing this post Magento 2 still has a hard dependency on PHPUnit version 4.1.0

I would also like to quickly mention Vinai Kopp’s Mage2Kata videos. These are a fantastic resource for Magento 2 testing and test-driven development(or TDD for short). These videos, and Vinai himself, have helped me to learn a lot of the intricacies of writing tests within the large, connected Magento 2 framework

Let’s get started!

The first thing we should do is ensure that our testing-related configuration files are properly set up.

To start writing our integration tests, we need to rename and update the contents of two files that the core provides for us as templates.

  • The first file is located at magento_root/dev/tests/integration/phpunit.xml.dist. Rename this file to phpunit.xml. You can leave the file in the same directory, but change the following line in the file to:
      const name=”TESTS_CLEANUP” value=”disabled”
  • This change will ensure that Magento isn’t uninstalled and reinstalled after each test, which will significantly speed up our integration test suite.
  • The second file is located at dev/tests/integration/etc/install-config-mysql.php.dist. Rename the file to install-config-mysql.php. Once again, leave it in the same folder and change the contents as follows:
    • The db-host, db-user, db-password, and db-name keys need to be changed so that Magento can connect to the integration test database. Also change the backend-frontname key’s value to whatever value you’re using on your store. (Ex. admin)

After those files have been updated, we can write the first test for our module. The first test will be an integration test to ensure that our module is configured properly and Magento can pick it up.

  • Q: Why bother testing the configuration of your modules?
  • A: Firstly, it’s an easy first test to write, especially when you’re trying to practice test-driven development. Secondly, these tests will quickly alert you to potential conflicts with the configuration of other modules, such as URL route name collisions.

The first test class file will be located at: /app/code/Demac/WelcomePlugin/Test/Integration/ModuleConfigTest.php and its contents are:

<?php

namespace Demac\WelcomePlugin\Test\Integration;


use Magento\Framework\Component\ComponentRegistrar;
use Magento\Framework\Module\ModuleList;
use Magento\TestFramework\ObjectManager;

class ModuleConfigTest extends \PHPUnit_Framework_TestCase
{
   private $subjectModuleName;

   /**
    * @var $objectManager ObjectManager
    */
   private $objectManager;

   protected function setUp()
   {
       $this->subjectModuleName = 'Demac_WelcomePlugin';
       /** @var ObjectManager objectManager */
       $this->objectManager = ObjectManager::getInstance();
   }

   public function testTheModuleIsRegistered()
   {
       $registrar = new ComponentRegistrar();
       $this->assertArrayHasKey('Demac_WelcomePlugin', $registrar->getPaths(ComponentRegistrar::MODULE));
   }

   public function testTheModuleIsConfiguredInTheTestEnvironment()
   {
       /** @var $moduleList ModuleList */
       $moduleList = $this->objectManager->create(ModuleList::class);
       $this->assertTrue($moduleList->has($this->subjectModuleName));
   }

   public function testTheModuleIsConfiguredInTheRealEnvironment()
   {
       /** @var $objectManager ObjectManager */
       $this->objectManager = ObjectManager::getInstance();

       // The tests by default point to the wrong config directory for this test.
       $directoryList = $this->objectManager->create(
           \Magento\Framework\App\Filesystem\DirectoryList::class,
           ['root' => BP]
       );
       $deploymentConfigReader = $this->objectManager->create(
           \Magento\Framework\App\DeploymentConfig\Reader::class,
           ['dirList' => $directoryList]
       );
       $deploymentConfig = $this->objectManager->create(
           \Magento\Framework\App\DeploymentConfig::class,
           ['reader' => $deploymentConfigReader]
       );

       /** @var $moduleList ModuleList */
       $moduleList = $this->objectManager->create(
           ModuleList::class,
           ['config' => $deploymentConfig]
       );
       $this->assertTrue($moduleList->has($this->subjectModuleName));
   }
}

If you followed our previous post, these tests should pass as you’ll have already created the registration.php and etc/module.xml files for this module as well as enabled your module via $ bin/magento module:enable Demac_WelcomePlugin

Next, we’ll write some tests to ensure that our plugin is registered within the proper application area and that it returns the expected values.

The test class file will be located at: /app/code/Demac/WelcomePlugin/Test/Integration/Plugin/WelcomePluginTest.php and its contents are:

<?php

namespace Demac\WelcomePlugin\Test\Integration\Plugin;


use Demac\WelcomePlugin\Plugin\WelcomePlugin;
use Magento\Framework\App\Area;
use Magento\TestFramework\App\State;
use Magento\TestFramework\Interception\PluginList;
use Magento\TestFramework\ObjectManager;

class WelcomePluginTest extends \PHPUnit_Framework_TestCase
{
   /**
    * @var ObjectManager
    */
   private $objectManager;

   private function setAppState($appState)
   {
       /** @var State $appState */
       $appState = $this->objectManager->create(State::class);
       $appState->setAreaCode($appState);
   }

   protected function setUp()
   {
       $this->objectManager = ObjectManager::getInstance();
   }

   protected function tearDown()
   {
       $this->setAppState(null);
   }

   public function testThePluginIsRegistered()
   {
       $this->setAppState(Area::AREA_FRONTEND);

       /** @var PluginList $pluginList */
       $pluginList = $this->objectManager->create(PluginList::class);
       $pluginInfo = $pluginList->get(WelcomePlugin::class, []);

       $this->assertSame(WelcomePlugin::class, $pluginInfo['demac_welcomeplugin']['instance']);
   }
}
  • A side note about the integration test cache: because we changed the TESTS_CLEANUP value to disabled in our phpunit.xml file, the configuration cache for integration tests will NOT automatically refresh. We have to manually delete the configuration folder. It’s found under /dev/tests/integration/tmp/ and starts with “sandfox-”.
  • In the above tests, I added a tearDown() method to reset the application area after each test so that my individual tests aren’t affecting each other. This method comes from the PHPUnit_Framework_TestCase class and is called after every test just as the setUp() method is called before every test.
  • For the above plugin integration test case, it’s a good idea to add tests to ensure that the plugin is not registered under the other application areas (global, adminhtml, webapi_rest, etc) and thus having unintended effects. But I’ll leave this exercise up to you.

Now let’s add our third test case: our unit tests.

/app/code/Demac/WelcomePlugin/Test/Unit/Plugin/WelcomePluginTest.php and its contents are:

<?php

namespace Demac\WelcomePlugin\Test\Unit\Plugin;


use Demac\WelcomePlugin\Plugin\WelcomePlugin;
use Magento\Framework\Message\ManagerInterface as MessageManager;


class WelcomePluginTest extends \PHPUnit_Framework_TestCase
{
  /**
   * @var WelcomePlugin
   */
  private $plugin;


  /**
   * @var MessageManager
   */
  private $messageManager;


  protected function setUp()
  {
      $this->messageManager = $this->getMockForAbstractClass(MessageManager::class, ['addSuccessMessage']);
      $this->plugin = new WelcomePlugin($this->messageManager);
  }


  public function testAfterSetCustomerDataAsLoggedInMethodCanBeCalled()
  {
      $session = $this->getMockBuilder(\Magento\Customer\Model\Session::class)
          ->disableOriginalConstructor()
          ->getMock();


      $this->messageManager->expects($this->once())
          ->method('addSuccessMessage')
          ->willReturn($this->messageManager);


      $this->assertSame(
          $session,
          $this->plugin->afterSetCustomerDataAsLoggedIn($session, $session)
      );
  }
}
  • I should point out that all of the dependencies, besides the subject under test (our Plugin class), are mocked out to ensure that the only thing affecting the outcome is the code being tested.
  • All of the mocking that I did in the above code was supported by the mocking features of PHPUnit and was not at all dependent on Magento 2.

Now just run your unit and integration tests. All you should see is green!

Now it’s your turn!

ecommerce, website design, demac media, magento 2, automated testing, magento ecommerce,magento 2 testing

Writing well-tested, maintainable modules in Magento 2 is just like any other skill; practice makes perfect. Start by simply testing your modules first. Then experiment with test-driven development to see where it does and doesn’t work for you.

Stay tuned for more Magento 2 content soon!

ecommerce, website design, demac media, magento 2, automated testing, magento ecommerce,magento 2 testing