Constraint
The Functional Testing Framework (FTF) constraint performs assertions after a test flow. A test flow is a set of test steps without assertions. Each constraint name must be globally unique in Magento application and must be placed in the module to which it belongs. Constraints run automatically after test flow has finished.
Constraint structure
Constraint
directory
A module in functional tests (<magento2_root_dir>/dev/tests/app/Magento/
) stores constraints in the Constraint
directory. The following image shows the Constraint
directory of the Magento_Widget module.
Constraint class
The constraint PHP class must:
-
Have unique name created using the following template
Assert{MagentoEntityName}{verification|action|place}
. For example:AssertUserSuccessDeleteMessage
corresponds toAssert{entityName}{verification}
AssertOrderPlaced
corresponds toAssert{entityName}{action}
AssertProductForm
corresponds toAssert{entityName}{place}
-
Extend the Magento\Mtf\Constraint\AbstractConstraint class.
-
Contain the following methods:
processAssert()
which contains assertions. APHPUnit_Framework_Assert
class (<magento2_root_dir>/dev/tests/functional/vendor/phpunit/phpunit/src/Framework/Assert.php
) can be used to simplify assertions.toString()
which returns a success message if the assertion is performed successfully
Constraint arguments
In the FTF, data set values are shared with a test class and constraints. A node name in data set can be complex like item1/item2/item3
. The argument name in processAssert()
must be the same as the item1
to transfer data from data set to constraint.
If a data set variable is used in the test, and is overwritten, it is transferred as altered to the constraint. Variables can be overwritten in the injectable test case class in test()
, __inject()
and __prepare()
methods, and then passed to the constraint class by return
. Furthermore, any returned value of these methods can be used as an argument in constraint.
An object that is not defined in the data set or isn’t returned from the test case is created using the Object Manager.
Let’s see the following images for the CreateSimpleProductEntityTest
test and the AssertProductPricesOnCategoryPage
constraint. Data set from the diagrams contains three variables with data: product
, category
and price
.
Green arrows show that product
value is transferred to the test and the constraint.
Orange arrows show that category
variable is transferred to the test directly, overwritten by testCreate()
method and only then transferred to constraint.
Blue arrow shows that price
value is transferred to the constraint only.
Constraint in the test
A test case’s constraints are nodes in variations of a data set. The data set has references to the PHP classes with assertions.
Constraints are performed in order they listed in the data set. However, you can use prev
(previous) and next
attributes to set your custom order.
1
2
3
<constraint name="Magento\Catalog\Test\Constraint\AssertCategorySaveMessage" next="Magento\Catalog\Test\Constraint\AssertCategoryForm"/>
<constraint name="Magento\Catalog\Test\Constraint\AssertCategoryForm" prev="Magento\Catalog\Test\Constraint\AssertCategorySaveMessage" next="Magento\Catalog\Test\Constraint\AssertCategoryPage"/>
<constraint name="Magento\Catalog\Test\Constraint\AssertCategoryPage" prev="Magento\Catalog\Test\Constraint\AssertCategoryForm" />
Constraint failure causes interruption of constraints execution within variation, and a test continues to perform from the next variation.
A test can contain constraints from different modules.
Be careful when you use constraints from another module. A module that is referred by constraint can be disabled, that fails in the test execution. It is safe to use constraints of different modules in one test case if that modules have hard dependencies.
The following example shows the <magento2_root_dir>/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/DeleteWidgetEntityTest.xml
data set with two constraints.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
<testCase name="Magento\Widget\Test\TestCase\DeleteWidgetEntityTest" summary="Delete Widget" ticketId="MAGETWO-28459">
<variation name="DeleteWidgetEntityTestVariation1">
<data name="widget/dataset" xsi:type="string">default</data>
<constraint name="Magento\Widget\Test\Constraint\AssertWidgetSuccessDeleteMessage" />
<constraint name="Magento\Widget\Test\Constraint\AssertWidgetAbsentOnFrontendHome" />
</variation>
</testCase>
</config>
Immediately after the test steps complete, both constraints are performed in the order listed.
Tagging
Tagging enables you to indicate what constraints must be called.
You can tag constraints in <module>/Test/etc/di.xml
using a severity
argument. Severity tagging of constraints is used for the customization of test run.
You can use the following severity tags:
high
middle
low
To assign severity tags do the following:
- Create
di.xml
file inTest/etc
of the module. - Assign
severity
to constraints in the following format:
1
2
3
4
5
<type name="Magento\[Module_name]\Test\Constraint\Assert...">
<arguments>
<argument name="severity" xsi:type="string">high|middle|low</argument>
</arguments>
</type>
For example, <magento2_root_dir>/dev/tests/functional/tests/app/Magento/CatalogRule/Test/etc/di.xml
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedProductPage">
<arguments>
<argument name="severity" xsi:type="string">high</argument>
</arguments>
</type>
<type name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedCatalogPage">
<arguments>
<argument name="severity" xsi:type="string">high</argument>
</arguments>
</type>
<type name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedShoppingCart">
<arguments>
<argument name="severity" xsi:type="string">high</argument>
</arguments>
</type>
</config>
How to create constraint
Use case: We want to assert widget availability in a widget grid.
Step 1. What module does it belong?
Widget grid and widget fixture are related to the Magento_Widget module.
Thus, we need to create constraint in the Magento_Widget module, in <magento2_root_dir>/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint
.
Step 2. What name should constraint have?
Using constraint naming principle, the constraint should be named as AssertWidgetInGrid
.
Step 3. Create <magento2_root_dir>/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetInGrid.php
with required structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
namespace Magento\Widget\Test\Constraint;
use Magento\Mtf\Constraint\AbstractConstraint;
/**
* Assert widget is available in widget grid.
*/
class AssertWidgetInGrid extends AbstractConstraint
{
/**
* Assert widget availability in widget grid.
*
* @return void
*/
public function processAssert()
{
}
/**
* Returns a string representation of the object.
*
* @return string
*/
public function toString()
{
return 'Widget is present in widget grid.';
}
}
Step 4. Implement assertion in processAssert()
Assertion logic: Take title of the widget from the widget fixture, open the page with a grid, check if the grid has our title.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
namespace Magento\Widget\Test\Constraint;
use Magento\Widget\Test\Fixture\Widget;
use Magento\Widget\Test\Page\Adminhtml\WidgetInstanceIndex;
use Magento\Mtf\Constraint\AbstractConstraint;
/**
* Assert widget is available in widget grid.
*/
class AssertWidgetInGrid extends AbstractConstraint
{
/**
* Assert widget availability in widget grid.
*
* @return void
*/
public function processAssert(Widget $widget, WidgetInstanceIndex $widgetInstanceIndex)
{
$filter = ['title' => $widget->getTitle()];
$widgetInstanceIndex->open();
\PHPUnit_Framework_Assert::assertTrue(
$widgetInstanceIndex->getWidgetGrid()->isRowVisible($filter),
'Widget with title \'' . $widget->getTitle() . '\' is absent in Widget grid.'
);
}
/**
* Returns a string representation of the object.
*
* @return string
*/
public function toString()
{
return 'Widget is present in widget grid.';
}
}
How to use constraint
To use constraint we’ve created in previous section, add a corresponding node to the data set of your test
1
<constraint name="Magento\Widget\Test\Constraint\AssertWidgetInGrid" />
in the order that it must be performed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
<testCase name="Magento\Widget\Test\TestCase\CreateWidgetEntityTest" summary="Create Widget" ticketId="MAGETWO-27916">
<variation name="CreateWidgetEntityTestVariation1">
<data name="widget/data/code" xsi:type="string">CMS Static Block</data>
<data name="widget/data/theme_id" xsi:type="string">Magento Luma</data>
<data name="widget/data/title" xsi:type="string">Title_%isolation%</data>
<data name="widget/data/store_ids/dataset" xsi:type="string">all_store_views</data>
<data name="widget/data/widget_instance/dataset" xsi:type="string">on_category</data>
<data name="widget/data/parameters/dataset" xsi:type="string">cmsStaticBlock</data>
<constraint name="Magento\Widget\Test\Constraint\AssertWidgetSuccessSaveMessage" />
<constraint name="Magento\Widget\Test\Constraint\AssertWidgetInGrid" />
<constraint name="Magento\Widget\Test\Constraint\AssertWidgetOnFrontendInCatalog" />
</variation>
</testCase>
</config>