Annotations in Magento 2 integration tests

I really like automated tests in Magento 2. They make everyone’s life easier and greatly contribute to overall product quality. Integration tests are an important part of Magento 2’s testing suite.

Integration testing

So far integration tests seem very interesting to me and I think that they make a lot of sense in Magento 2. There are also functional and unit tests that are also important. It’s just that I think that integration tests are really useful in many cases.

The main purpose of integration tests is to find out how different modules work together. I wrote a bit about integration tests in general here.

Untitled Diagram (1)

One of the php programming language features that is used in Magento 2 integrations tests are annotations.

Annotations

In php, annotations are meta data that are used to inject some behaviour. This is achieved by placing that data into the comments above the structure they are referring to.

Some people from the php community support annotations while others are against them. Clearly this approach has gained much popularity in the last few years and some of the leading projects such as Symfony and Doctrine use them. I think their usefulness is explained nicely here.

Annotations in Magento 2 Integration testing framework

Let’s take a look at one of the tests from Magento core testsuite.

dev/tests/integration/testsuite/Magento/Backend/Model/Auth/SessionTest.php

/**
 * Disabled form security in order to prevent exit from the app
 * @magentoConfigFixture current_store admin/security/session_lifetime 59
 */
public function testIsLoggedInWithIgnoredLifetime()
{
    $this->_auth->login(
        \Magento\TestFramework\Bootstrap::ADMIN_NAME,
        \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD
    );
    $this->assertTrue($this->_model->isLoggedIn());

    $this->_model->setUpdatedAt(time() - 101);
    $this->assertTrue($this->_model->isLoggedIn());
}

The purpose of this test is to check if the logged in user is logged out after some period of time. We need to set the configuration value before this test and we do this with magentoConfigFixture annotation. Explanation follows.

How annotations work

Integration tests in Magento 2 are based on PHPUnit.

Annotations from docblocks are internally gathered with PHP’s reflection API’s getDocComment method which has the ability to introspect the method and return it’s doc block (where the annotations are). PHPUnit uses this functionality with getAnnotations method that returns the annotations as array.

There is a feature in PHPUnit that is used for tracking events that occur during tests execution. Events such as onTestStart, onTestSuccess, onTestFailure, etc. The class that makes this possible is PHPUnit_Framework_TestListener.

Magento has a wrapper event class over PHPUnit_Framework_TestListener that fires it’s own events and those call specific annotation classes that are registered as listeners.

In our example @magentoConfigFixture is used to set the config value. The Annotation class ConfigFixture from Magento\TestFramework\Annotation is registered as a listener. Once the test runs, the startTest method from that class will be called and set configuration values:

/var/www/html/magento2/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php

/**
 * Handler for 'startTest' event
 *
 * @param \PHPUnit_Framework_TestCase $test
 */
public function startTest(\PHPUnit_Framework_TestCase $test)
{
    $this->_currentTest = $test;
    $this->_assignConfigData($test);
}

Annotations

@magentoConfigFixture

As mentioned, you can use this annotation when you need to set some config value before the test starts. The format for this annotation is:

* @magentoConfigFixture store_code section/group/field value

It will set the value for the configuration field with the path section/group/field for store that has the “store_code”.

@magentoDbIsolation

I think this one is very useful. When enabled, each of your tests will be wrapped inside a transaction and rolled back afterwards.

* @magentoDbIsolation enabled

You can enable it when you want to make sure that your tests don’t affect each other.

When it’s disabled you will be able to check the values in database after the tests ran which makes debugging easier.

This feature can be used on class level and on method level.

@magentoDataFixture

* @magentoDataFixture Magento/Catalog/_files/product_simple.php

This annotation is used when you want to run your custom code before the test. This can be any piece of code contained in a script.

You can also provide a rollback script that is supposed to revert the changes that your fixture made after the test has finished. These are suffixed with “_rollback”.

This fixture adds a product and removes it on rollback:

dev/tests/integration/testsuite/Magento/Catalog/_files/product_special_price.php

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

/** @var $product \Magento\Catalog\Model\Product */
$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product');
$product->setTypeId('simple')
    ->setAttributeSetId(4)
    ->setWebsiteIds([1])
    ->setName('Simple Product')
    ->setSku('simple')
    ->setPrice(10)
    ->setMetaTitle('meta title')
    ->setMetaKeyword('meta keyword')
    ->setMetaDescription('meta description')
    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
    ->setStockData(['use_config_manage_stock' => 0])
    ->setSpecialPrice('5.99')
    ->save();

dev/tests/integration/testsuite/Magento/Catalog/_files/product_special_price_rollback.php

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

/** @var \Magento\Framework\Registry $registry */
$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry');

$registry->unregister('isSecureArea');
$registry->register('isSecureArea', true);

$repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
    'Magento\Catalog\Model\ProductRepository'
);
try {
    $product = $repository->get('simple');
    $product->delete();
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
    //Entity already deleted
}

$registry->unregister('isSecureArea');
$registry->register('isSecureArea', false);

@magentoAppIsolation

When this annotation is enabled, the application will be restarted on each test run.

@magentoCache

Simply determines whether the cache is enabled or disabled. Useful for many scenarios.

@magentoComponentsDir

Includes all the files from the specific directory. This fixture is usually used for adding email templates to the test.

@magentoAdminConfigFixture

Similar to magentoConfigFixture except that it sets values for default scope so you cannot pick stores.

@magentoAppArea

Defines application area in cases where this is relevant.

@magentoDataFixtureBeforeTransaction

These fixtures will be executed, as the name suggests, before transaction that is enabled by magentoDbIsolation so they won’t be affected by that.

Conclusion

As you can see, annotations in Magento 2 integration testing framework provide all kinds of useful features. You can get a better insight of them by looking at the existing integration tests. Let me know what you think.

Would you like to recommend this article to your friends or colleagues?
Feel free to share.

facebooktwittergoogle_plusredditlinkedin

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*

*