layout | group | subgroup | title | menu_title | menu_order | version | github_link |
---|---|---|---|---|---|---|---|
default |
mtf-guide |
40_Approach |
Create a test in the Functional Testing Framework |
New functional test. Practice |
3 |
2.0 |
mtf/create_test/new_test.md |
To demonstrate the usage of test components from previous sections in the test creation process, we will create a new functional injectable test step-by-step. Before creating automated test, try to pass it manually.
To make the documentation more consistent, we created a completely new test specially for this tutorial. We used the concrete Magento commit functionality for this test. You can install one and try to follow this guide.
Create a synonym group (synonyms are a way to expand the scope of eligible matching products) with:
- Scope:
- All Websites
- All Store Views
- Default Store View
- Synonyms: shoes, foot wear, men shoes, women shoes.
Variation 1:
- Log in to Admin.
- Browse to "Marketing" > "SEO & Search" > "Search Synonyms".
- Click the "New Synonyms Group" button.
- Enter data in the "Synonyms" field.
- Click the "Save Synonym Group" button.
- Verify the synonym group saved successfully
Variation 2:
- Log in to Admin.
- Browse to "Marketing" > "SEO & Search" > "Search Synonyms".
- Click the "New Synonyms Group" button.
- Select "All Store Views" in a "Scope" field.
- Enter data in the "Synonyms" field.
- Click the "Save Synonym Group" button.
- Verify the synonym group saved successfully
Variation 3:
- Log in to Admin.
- Browse to "Marketing" > "SEO & Search" > "Search Synonyms".
- Click the "New Synonyms Group" button.
- Select "Default Store View" in a "Scope" field.
- Enter data in the "Synonyms" field.
- Click the "Save Synonym Group" button.
- Verify the synonym group saved successfully
- Log in to Admin.
- Open the Search Synonym page.
- Click the "New Synonym Group" button.
- Enter data according to a data set. For each variation, the synonyms must have unique identifiers.
- Click the "Save Synonym Group" button.
- Verify the synonym group saved successfully
-
Adjust configuration. Learn how to adjust a configuration.
-
Prepare Magento application. Learn how to prepare Magento application.
-
Prepare environment for test run. Learn how to prepare environment for test run.
This step is applicable if a fixture doesn't exist in a module.
Use a generateFixtureXml.php
to create a new fixture.
Enter in your terminal:
cd <magento2_root_dir>/dev/tests/functional/utils
php -f generateFixtureXml.php -- --name synonym --entity_type search_synonyms --collection Magento\\Search\\Model\\ResourceModel\\Query\\Collection
See the following explanations.
Parameter | Value | Explanation |
---|---|---|
--name |
synonym |
A name of the fixture. It can have any name. synonym seems to be logical. |
--entity_type |
search_synonyms |
Database table name where entity data is stored. You can track database input when you perform a manual testing. A new record will be created in a table that you need. |
--collection |
Magento\\Search\\Model\\ResourceModel\\Query\\Collection |
Collection to generate data sets. Synonyms are the entities of a Magento_Search module. A collection can always be found in model resources. |
All slashes must be escaped with \\
.
As a result of previous commands, a brand new fixture can be found in the <magento2_root_dir>/dev/tests/functional/tests/app/Magento/Search/Test/Fixture
directory.
The following is a code of the new Synonym fixture.
{%highlight xml%}
{%endhighlight%}If we open a New Synonym Group page in a browser

we see that store_id
and website_id
are combined in the "Scope" fields. To set store_id
and website_id
, we have to perform some more logic than just entering the data. That's why we should use a data source.
The same field is present in Magento_Widget module. It means that data source has been already written and we can reuse it.
Let's check the functional tests for the Magento_Widget module.

It contains a StoreIds.php
data source, that is similar to what we need. It has the following code:
{% highlight php %}
params = $params; if (isset($data['dataset'])) { $dataset = explode(',', $data['dataset']); foreach ($dataset as $store) { /** @var Store $store */ $store = $fixtureFactory->createByCode('store', ['dataset' => $store]); if (!$store->hasData('store_id')) { $store->persist(); } $this->stores[] = $store; $this->data[] = $store->getName(); } } else { $this->data[] = null; } } /** * Return stores. * * @return Store */ public function getStores() { return $this->stores; } } {% endhighlight %} The difference is that it is designed for multiple stores, but we don't need that. Adding some changes we can get our data source. {% highlight php %} params = $params; if (isset($data['dataset'])) { $store = $fixtureFactory->createByCode('store', ['dataset' => $data['dataset']]); if (!$store->hasData('store_id')) { $store->persist(); } $this->store = $store; $this->data = $store->getName(); } else { $this->data = $data; } } /** * Return store. * * @return Store */ public function getStore() { return $this->store; } } {% endhighlight %} This data source: 1. Checks if a field has a `dataset` key in a value that comes from a variation. If it doesn't, then field is assigned a value from the variation. 2. If it does, then a new Store fixture is created with a `dataset` from a Store repository (`/dev/tests/functional/tests/app/Magento/Store/Test/Repository/Store.xml`). 3. Checks if the `store_id` field exists in the Store fixture. If it doesn't, a new Store in Magento is created. 4. Returns a Store `name` value. We should save it as `/dev/tests/functional/tests/app/Magento/Search/Test/Fixture/Synonym/ScopeId.php`.  Now we should change the fixture. Instead of `store_id` and `website_id`, we must use `scope_id` with the `Magento\Search\Test\Fixture\Synonym\ScopeId` data source class. {% highlight xml %} ... ... ... ... ... ... {% endhighlight %} Then, we must regenerate the fixture to apply changes: php /dev/tests/functional/utils/generate.php A new PHP class `Synonym.php` is generated in `/dev/tests/functional/generated/Magento/Search/Test/Fixture`. {%highlight php%} '0', ]; /** * @var array */ protected $synonyms = [ 'is_required' => '0', ]; /** * @var array */ protected $scope_id = [ 'is_required' => '0', 'source' => 'Magento\Search\Test\Fixture\Synonym\ScopeId', ]; /** * @return mixed */ public function getGroupId() { return $this->getData('group_id'); } /** * @return mixed */ public function getSynonyms() { return $this->getData('synonyms'); } /** * @return mixed */ public function getScopeId() { return $this->getData('scope_id'); } } {%endhighlight php%} #### Step 3. Create the initial test case {#create-init-test-case} Now we can create a [test case][]. From the [test case topic][] we know about the structure, location and name of the test case. In this example it is named `CreateSynonymEntityTest.php` and stored in `/dev/tests/functional/tests/app/Magento/Search/Test/TestCase`.  As a result of [manual testing][] we know that we must work with a Search Synonym Index page and a New Synonym Group page during the test flow. We can code the initialization of these pages in the test using an `__inject()` method of the `Magento\Mtf\TestCase\Injectable` class. The pages will be created in [Step 5][]. Also, we will use the fixture from the [Step 2][]. {% highlight php %} synonymsIndex = $synonymsIndex; $this->synonymsNew = $synonymsNew; } /** * Create Synonym group test. * * @param Synonym $synonym * @return void */ public function test(Synonym $synonym) { // Steps } } {% endhighlight %} #### Step 4. Create the data set {#create-data-set} Now we can add a [data set][] with variations that cover cases in the [test description][]: `/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/CreateSynonymEntityTest.xml`  The following code contains a data set, but doesn't have data yet: {% highlight xml %} enter data and constraints for vaiation 1 enter data and constraints for vaiation 2 enter data and constraints for variation 3{% endhighlight %}
According to a New Synonym Group form we need to enter data in the synonyms
and scope_id
fields.
synonyms
field. We need to set data to a fixture field. The name of the field should be<name of a fixture>/data/<name of the field>
. It isname = "synonym/data/synonyms"
. To make data unique in each variation, we can use the%isolation%
placeholder.scope_id
field. We need to set data to a fixture field from a repository. The name of the field should be<name of a fixture>/data/<name of the field>/dataset
. It isname="synonym/data/scope_id/dataset"
. As you remember from Step 2, we use the data source to process this field. The data source loads the Store fixture with the Store repository, and returns the name of the field we need. In adataset
value, we should specify a name of the Store repositorydataset name
from<magento2_root_dir>/dev/tests/functional/tests/app/Magento/Store/Test/Repository/Store.xml
.
| Variation # |synonyms
|scope_id
|---
|variation 1|shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation%
|In this variation we won't use this field to cover All Website
case, because it is selected automatically when the New Synonym Group page is opened
|variation 2|shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation%
|all_store_views
|variation 3|shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation%
|default_store_view
Let's see the data set with data.
{% highlight xml %}
shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation% shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation% all_store_views shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation% default_store_view{% endhighlight %}
A bit later we will add assertions to complete our data set.
In Step 3, we added two pages to the test case class. Because both pages are in the Admin area, we should create them in the <magento2_root_dir>/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml
directory.
SynonymsIndex.xml
{% highlight xml %}
{% endhighlight %}
SynonymsNew.xml
{% highlight xml %}
{% endhighlight %}
To generate PHP classes for these pages enter and run in your terminal
php <magento2_root_dir>/dev/tests/functional/utils/generate.php
In the next step we will create blocks that implements logic in these pages.
Let's see in the test description what actions must be performed:
- Click the "New Synonym Group" button.
- Enter data according to a data set.
- Click the "Save Synonym Group" button.
How to code 'Click the "New Synonym Group" button'
Fortunately, you already have a block that contains a method to add a new entity in a grid: \Magento\Backend\Test\Block\GridPageActions
.
{% highlight php startinline=1 %}
/**
- Click the "Add New" button
- @return void */ public function addNew() { $this->_rootElement->find($this->addNewButton)->click(); }
{% endhighlight %}
In HTML page, to locate the UI block that contains a button, we will use a .page-main-actions
locator.
The SynonymsIndex.xml page must contain the following block to be able to run the method in a test case.
{% highlight xml %}
{% endhighlight %}
Now you can run generate.php
as we did before to re-generate page classes.
How to code 'Enter data according to a data set'
We need to enter data from a data set into the form fields.
The Block
directory in the Magento_Search module (in the Magento code) contains the Adminhtml/Synonyms/Edit
directories, as shown below:
The Search/Test
directory in functional tests should be constructed in a similar manner:
We need a fill()
method from the \Magento\Mtf\Block\Form
class and a mapping file.
Form mapping
We don't need to define mapping parameters for the synonyms
field, because they are the same as the default values. (See the nodes description table.) The same is applicable to the scope_id
field except a type of input element, which is a custom typified element \Magento\Mtf\Client\Element\SelectstoreElement
in our case. Let's create the mapping file SynonymsForm.xml
, which has the following code:
{% highlight xml %}
selectstore{% endhighlight %}
A block class must simply extend \Magento\Mtf\Block\Form
class. Its name duplicates the name of the mapping file that is a concatenation of the fixture name and a Form
ending (Synonyms
+Form
). Let's create a \Magento\Search\Test\Block\Adminhtml\Synonyms\Edit\SynonymsForm
empty class:
{% highlight php %}
{% endhighlight %} **How to code 'Click the "Save Synonym Group" button'** The `save()` method from the [`\Magento\Backend\Test\Block\FormPageActions`][] block class allows you to click the "Save Synonym Group" button. The `SynonymsNew.xml` page must contain this class. The `.page-main-actions` css selector will help to identify a UI block with the button on the HTML page. {% highlight xml %} {% endhighlight %} #### Step 7. Add the blocks to pages {#add-blocks-to-pages} In previous step, we created blocks with methods that enable us to perform the required test flow. To associate methods with [pages][], blocks must be added to pages. **Search Synonym page** A corresponding page object in a functional test is `/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymsIndex.xml` The page with a block: {% highlight xml %}{% endhighlight %}
New Synonym Group page
A corresponding page object in a functional test is <magento2_root_dir>/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymsIndex.xml
The page with blocks:
{% highlight xml %}
{% endhighlight %}
To generate PHP classes for these pages, enter the following command from your terminal.
php <magento2_root_dir>/dev/tests/functional/utils/generate.php
Now we can define the test flow in a test()
method of the test case (Step 3).
Here we should recall Step 3, where the initial test case was created.
An argument for the test()
method is a test object (a fixture).
{% highlight php startinline=1 %}
/**
- Create Synonym group test.
- @param Synonym $synonym
- @return void */ public function test(Synonym $synonym) { // Steps }
{% endhighlight %}
Now we can add page classes made in Step 5:
{% highlight php startinline=1 %}
use Magento\Search\Test\Page\Adminhtml\SynonymsIndex; use Magento\Search\Test\Page\Adminhtml\SynonymsNew;
{% endhighlight %}
All methods are defined in blocks (Step 6) that are grouped in pages (Step 5, Step 7).
Remember our test flow:
- Log in to Admin
- Open the Search Synonym page
- Click the "New Synonym Group" button
- Enter data according to a data set
- Click the "Save Synonym Group" button
Let's code it!
Log in to Admin and open the Search Synonym page
In the FTF, the process of logging in doesn't require a special method and is performed automatically when any page from the Admin is opened. A method, which we will use, is an open()
method of the Magento/Mtf/Page/BackendPage
class. There is no need to add this class in use
, because it is inherited from the Magento/Search/Test/Page/Adminhtml/SynonymsIndex
class.
{% highlight php startinline=1 %}
$this->synonymsIndex->open();
{% endhighlight %}
Click the "New Synonym Group" button
To Click the "New Synonym Group" button, we will use the addNew()
method from the pageActionsBlock
block. A getPageActionsBlock()
of the generated Magento/Search/Test/Page/Adminhtml/SynonymsIndex
class receives parameters defined in the pageActionsBlock
block (class
, locator
, strategy
).
{% highlight php startinline=1 %}
$this->synonymsIndex->getPageActionsBlock()->addNew();
{% endhighlight %}
This action opens the New Synonym Group page.
Enter data according to a data set
To enter data in the form, we use the fill()
method from the synonymForm
block of the synonymsNew
page. An argument for this method is a fixture Synonym
. A getSynonymForm()
method of the generated Magento/Search/Test/Page/Adminhtml/SynonymsNew
class receives parameters defined in the synonymForm
block.
{% highlight php startinline=1 %}
$this->synonymsNew->getSynonymForm()->fill($synonym);
{% endhighlight %}
Click the "Save Synonym Group" button
A save()
method with parameters defined in a formPageActions
block. Parameters are injected using a getFormPageActions()
method from the synonymsNew
page (generated Magento/Search/Test/Page/Adminhtml/SynonymsNew
page class).
{% highlight php startinline=1 %}
$this->synonymsNew->getFormPageActions()->save();
{% endhighlight %}
Full test()
definition
{% highlight php startinline=1%}
/**
- Create Synonym group test.
- @param Synonym $synonym
- @return void
*/
public function test(Synonym $synonym)
{
// Steps
$this->synonymsIndex->open(); // logs in to Admin, opens Search Synonyms page
$this->synonymsIndex->getPageActionsBlock()->addNew(); // receiving of the page action block with '_rootElement' containing locator which is indicated in the page class for PageActionBlock from the page, makes 'click' action on it
$this->synonymsNew->getSynonymForm()->fill($synonym); // enters data from variation in the New Synonym Group fields
$this->synonymsNew->getFormPageActions()->save(); //
click
on the Save Synonym Group button }
{% endhighlight %}
The test is now ready to run. It is complete, except for an assertion that we will create in the next step.
The full test case code:
{% highlight php %}
synonymsIndex = $synonymsIndex; $this->synonymsNew = $synonymsNew; } /** * Create Synonym group test. * * @param Synonym $synonym * @return void */ public function test(Synonym $synonym) { // Steps $this->synonymsIndex->open(); $this->synonymsIndex->getPageActionsBlock()->addNew(); $this->synonymsNew->getSynonymForm()->fill($synonym); $this->synonymsNew->getFormPageActions()->save(); } } {% endhighlight %} You can run the test using your IDE or the CLI. The Selenium Server must be [up and running][]. To run the test using the CLI, enter in your terminal: cd /dev/tests/functional vendor/bin/phpunit --filter CreateSynonymEntityTest The test will be performed in a browser. Three synonyms groups are created one by-one that corresponds to three variations in a data set. #### Step 10. Create the assertion {#create-assertion} The last item in the test description says that the test must check that a success message is displayed after the test flow completes.  To cover this, we should create the test assertion ([constraint][]) and add the full class name to a variation of the data set. Fortunately, this type of assertion is commonly used in functional tests. If we search on the phrase "SuccessSaveMessage" in `/dev/tests/functional`, there will be several matches. Let's select from the list of results a [`\Magento\Customer\Test\Constraint\AssertCustomerSuccessSaveMessage`][] class. It has the following code: {% highlight php %} getMessagesBlock()->getSuccessMessage(); \PHPUnit_Framework_Assert::assertEquals( self::SUCCESS_MESSAGE, $actualMessage, 'Wrong success message is displayed.' . "\nExpected: " . self::SUCCESS_MESSAGE . "\nActual: " . $actualMessage ); } /** * Text success save message is displayed. * * @return string */ public function toString() { return 'Assert that success message is displayed.'; } } {% endhighlight %} By making a simple change, we can create a constraint class that is needed `\Magento\Search\Test\Constraint\AssertSynonymSuccessSaveMessage`  with the following code: {% highlight php %} getMessagesBlock()->getSuccessMessage(); \PHPUnit_Framework_Assert::assertEquals( self::SUCCESS_MESSAGE, $actualMessage, 'Wrong success message is displayed.' . "\nExpected: " . self::SUCCESS_MESSAGE . "\nActual: " . $actualMessage ); } /** * Text success save message is displayed * * @return string */ public function toString() { return 'Assert that success message is displayed.'; } } {% endhighlight %} To handle the messages we use the `\Magento\Backend\Test\Block\Messages` class, by adding the `messagesBlock` block to the `SynonymsIndex` page. In `/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerGroupIndex.xml`, we can see that the following block is used: {% highlight xml %} {% endhighlight %} This block must be added to `SynonymsIndex` class. To do this: 1) Open `dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymsIndex.xml`. 2) Add the block node: {% highlight xml %} {% endhighlight %} 3) Launch the generating tool to update the page class: php /dev/tests/functional/utils/generate.php And now we can add `` to each variation of a data set `/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/CreateSynonymEntityTest.xml`: {% highlight xml %} shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation% shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation% all_store_views shoes %isolation%, foot wear %isolation%, men shoes %isolation%, women shoes %isolation% default_store_view{% endhighlight %}
The test is ready to run.
You can run the test using your IDE or the CLI. The Selenium Server must be up and running. To run the test using the CLI, enter in your terminal:
cd <magento2_root_dir>/dev/tests/functional
vendor/bin/phpunit --filter CreateSynonymEntityTest
The test now checks after each variation whether a success message is displayed.
That's it!
*[FTF]: Functional Testing Framework *[CRUD]: Create Read Update Delete *[IDE]: Integrated Development Environment *[CLI]: Command Line Interface