Vinai Kopp

Magento Expert, Developer & Trainer

  • 01. The Skeleton Module Kata

    February 5, 2016

    Mage2Katas

    Welcome to the first episode of Mage2Katas!

    This series is not an introduction into how to set up or run PHPUnit, instead it is a bunch of examples of how to test drive common Magento 2 development steps.

    The screencasts can be used to see TDD in action - which is how I prefere to learn things. They can also be used to just gain insight into aspects of Magento 2 development, even if not following TDD (which would be foolish knowing the benefits of TDD).

    This is the blogpost for the first episode of the Screencast.

    We are going to start at the beginning: creating an extension skeleton test first.

    Typical for Magento, the first kata won’t include unit tests, but instead utilize the Magento 2 integration test framework.
    This is because writing configuration is an integral part of development in Magento, and the parsing and processing of it is done mostly by core classes.
    I expect to write a lot more unit tests in future katas.

    The values this kata provides:

    • Gain experience using the integration test framework
    • Gain insight into Magento 2 module architecture
    • Practice always following the rules of TDD
    • Detect incomplete deployments during continuous integration (CI)

    To use the Magento 2 integration tests I have set up and configured the test DB using dev/tests/integration/etc/install-config-mysql.php. I’m using the standard Magento 2 integration test framework, I’ve only added <ini name="memory_limit" value="-1"/> to a local phpunit.xml file because I’ve set up my PHP to always use a memory limit, even for php-cli.

    See the Magento 2 devdocs for more detailed information on how to setup and execute integration tests.

    Enough talking, lets jump right in.


    The module for this kata is called Mage2Kata_SkeletonModule.

    According to the first rule of TDD I’m not allowed to write any production code until I have a failing test.
    The first thing I create for a new module mostly is the registration.php file.

    Now, where can I put a test to check a module is registered correctly.
    For integration tests there are several options.

    • The Magento 2 core team puts them mostly into dev/tests/integration/testsuite. Since the core tests are all under the Magento namespace, we could simply add our test in there under our own namespace.
    • Since the integration tests we are about to create clearly belong to a specific module, we could put them into the module directory itself.
    • Or we could create our own sub directory within dev/tests/integration.
      Personally I’ve used all of these, depending on what made the most sense in a given situation.

    This time I think putting the tests into the module directory is the best fit.

    I’ll start by creating the directory app/code/Mage2Kata/SkeletonModule/Test/Integration/.
    Tests is this directory will not be picked up by the Magento 2 test framework by default, so I’ll add that to the local dev/tests/integration/phpunit.xml file. This only needs to be done once if done correctly.

    Next lets create the first test.
    I follow Uncle Bobs example and always start with a dummy test to check that my environment is set up correctly.

    <?php
     
    namespace Mage2Kata\SkeletonModule;
     
    class SkeletonModuleTest extends \PHPUnit_Framework_TestCase
    {
        public function testNothing()
        {
            $this->fail('I expect this message');
        }
    }

    And indeed, the test fails.


    Now we know the test is being executed, so we can write the first real test.

    <?php
     
    namespace Mage2Kata\SkeletonModule;
     
    use Magento\Framework\Component\ComponentRegistrar;
     
    class SkeletonModuleTest extends \PHPUnit_Framework_TestCase
    {
        public function testTheModuleIsRegistered()
        {
            $registrar = new ComponentRegistrar();
            $paths = $registrar->getPaths(ComponentRegistrar::MODULE);
            $this->assertArrayHasKey('Mage2Kata_SkeletonModule', $paths);
        }
    }

    Wow, we broke the integration test framework. This happens because by default the framework cleans up the environment during the integration test bootstrap. This makes sense for test isolation and should always be turned on for CI, but I often turn this off when writing tests, since it also reduces the test execution time.

    <const name="TESTS_CLEANUP" value="disabled"/>

    Thanks to Kristof a.k.a. Fooman for pointing this setting in the phpunit.xml file out to me while talking about the integration test execution time.

    With this setting turned off, the test fails for the right reason.

    Lets make it pass by adding the app/code/Mage2Kata/SkeletonModule/registration.php file. I’ve added a basic file template to PHPStorm for this.

    <?php
     
    use Magento\Framework\Component\ComponentRegistrar;
     
    ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Mage2Kata_SkeletonModule', __DIR__);

    And with this our first test is green.


    What next? I want us to add the etc/module.xml file next. But first, we want to have a failing test.

    public function testTheModuleIsKnownAndEnabled()
    {
        $objectManager = ObjectManager::getInstance();
        /** @var ModuleList $moduleList */
        $moduleList = $objectManager->create(ModuleList::class);
        $this->assertTrue(
            $moduleList->has('Mage2Kata_SkeletonModule'),
            'The module Mage2Kata_SkeletonModule is not enabled'
        );
    }

    To make it pass we add the etc/module.xml file.

    <?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
        <module name="Mage2Kata_SkeletonModule" setup_version="0.1.0">
        </module>
    </config>

    Lets run the test… still no luck! This is a consequence of us disabling test cleanup earlier. We have to do it manually now if needed.
    We need to remove the dev/tests/integration/tmp/sandbox-0-* directory. It is automatically regenerated.

    And now the test passes.


    We now know the module.xml and the registration.php are correct. But we don’t know if the module is actually enabled in the real non-testing environment.
    We can check using bin/magento module:status.

    Automating this test allows us to detect incomplete deployments in CI.

    The next test should fail until the module is enabled in the real environment.

    But first a little refactoring.

    <?php
     
    namespace Mage2Kata\SkeletonModule;
     
    use Magento\Framework\Component\ComponentRegistrar;
    use Magento\Framework\Module\ModuleList;
    use Magento\TestFramework\ObjectManager;
     
    class SkeletonModuleTest extends \PHPUnit_Framework_TestCase
    {
        private $moduleName = 'Mage2Kata_SkeletonModule';
     
        /**
         * @var ObjectManager
         */
        private $objectManager;
     
        protected function setUp()
        {
            $this->objectManager = ObjectManager::getInstance();
        }
     
        public function testTheModuleIsRegistered()
        {
            $registrar = new ComponentRegistrar();
            $paths = $registrar->getPaths(ComponentRegistrar::MODULE);
            $this->assertArrayHasKey($this->moduleName, $paths);
        }
     
        public function testTheModuleIsKnownAndEnabledInTheTestEnvironment()
        {
            /** @var ModuleList $moduleList */
            $moduleList = $this->objectManager->create(ModuleList::class);
            $message = sprintf('The module "%s" is not enabled in the test environment', $this->moduleName);
            $this->assertTrue($moduleList->has($this->moduleName), $message);
        }
    }

    Lets check everything still passes and we didn’t break anything during the refactoring… ok.

    Now we can add the new test.
    We need to somehow tell it to use the real etc directory instead of the integration test etc directory.
    This is Magento 2, so it’s bound to be injected somewhere. We just need to find the right class.
    After a little digging, this is what I ended up with.

    public function testTheModuleIsKnownAndEnabledInTheRealEnvironment()
    {
        $directoryList = $this->objectManager->create(DirectoryList::class, ['root' => BP]);
        $configReader = $this->objectManager->create(DeploymentConfigReader::class, ['dirList' => $directoryList]);
        $deploymentConfig = $this->objectManager->create(DeploymentConfig::class, ['reader' => $configReader]);
     
        /** @var ModuleList $moduleList */
        $moduleList = $this->objectManager->create(ModuleList::class, ['config' => $deploymentConfig]);
        $message = sprintf('The module "%s" is not enabled in the real environment', $this->moduleName);
        $this->assertTrue($moduleList->has($this->moduleName), $message);
    }

    It contains some nice examples how the object manager allows us to specify constructor arguments if needed.

    The test still fails as expected, since the module so far has not been enabled. To make it pass all we need to enable the module in the real environment.
    Lets run bin/magento module:enable Mage2Kata_SkeletonModule.

    And all is green.

    Thats all for this time, hope you liked it.

    Since this was a kata, the benefit of it is learning, so the final step is deleting all the code we wrote.
    I’m serious - go ahead and delete all the code from the kata right now.
    This will help us to practice further.

    I invite you to do this kata once a day for a week or so, until you can do it without the video for guidance.

    I’m always happy to discuss testing topics. The easiest way to reach me is to leave a comment here, or on Twitter at @VinaiKopp. Or maybe we can see each other in real life at a conference.

    comments powered by Disqus