Data fixture attribute

Data fixture attributes apply fixtures that implement Magento\TestFramework\Fixture\DataFixtureInterface or Magento\TestFramework\Fixture\RevertibleDataFixtureInterface.

It takes two more optional parameters alongside the fixture class name. The second parameter is the data that is used to customize the fixture and the third parameter is the alias (ID) of the fixture that is used to retrieve the data returned by the fixture and also as a reference in other fixture parameters.

Use data fixtures to prepare a database for tests. The Integration Testing Framework (ITF) reverts the database to its initial state automatically. To set up a date fixture, use the DataFixture attribute.

Format

#[
    DataFixture(string $type, array $data = [], ?string $as = null, ?string $scope = null, int $count = 1)
]

Parameters

Fixture Usage

Retrieve fixture data in the test

A test can retrieve data that was returned by a Data Fixture using Magento\TestFramework\Fixture\DataFixtureStorageManager and the fixture alias.

The following example shows how to retrieve data that was returned by the fixtures:

class ProductsList extends \PHPUnit\Framework\TestCase
{
    #[
        DataFixture(ProductFixture::class, as: 'product1'),
        DataFixture(ProductFixture::class, as: 'product2'),
        DataFixture(ProductFixture::class, as: 'product3')
    ]
    public function testGetProductsCount(): void
    {
        $fixtures = DataFixtureStorageManager::getStorage();
        $product1 = $fixtures->get('product1');
        $product2 = $fixtures->get('product2');
        $product3 = $fixtures->get('product3');
    }
}

Supply data to data fixture as a variable

It is possible to supply data as a variable from one fixture to another using the fixture alias in one of the following formats:

The following example shows how a fixture can use the data of another fixture:

class QuoteTest extends \PHPUnit\Framework\TestCase
{
    #[
        DataFixture(GuestCartFixture::class, as: 'cart'),
        DataFixture(SetBillingAddressFixture::class, ['cart_id' => '$cart.id$']),
    ]
    public function testCollectTotals(): void
    {
    }
}

Specifying the number of instances of data fixture to generate

data-variant=info
data-slots=text
Sometimes, we need to generate several instances of a data fixture with exactly the same configuration. For such cases, we can use the count parameter and set its value to the desired number of instances.
class ProductsList extends \PHPUnit\Framework\TestCase
{
   #[
      DataFixture(CategoryFixture::class, ['is_anchor' => 0], 'category1'),
      DataFixture(CategoryFixture::class, ['parent_id' => '$category1.id$'], 'category2'),
      DataFixture(ProductFixture::class, ['category_ids' => ['$category1.id$']])
      DataFixture(ProductFixture::class, ['category_ids' => ['$category2.id$']], count: 2)
   ]
   public function testAddCategoryFilter(): void
   {
      $fixtures = DataFixtureStorageManager::getStorage();
      $category1 = $fixtures->get('category1');
      $category2 = $fixtures->get('category2');
      $collection = $this->collectionFactory->addCategoryFilter($category2);
      $this->assertEquals(2, $collection->getSize());
      $collection = $this->collectionFactory->addCategoryFilter($category1);
      $this->assertEquals(1, $collection->getSize());
   }
}

Or in case where we need to specify an alias:

class ProductsList extends \PHPUnit\Framework\TestCase
{
   #[
      DataFixture(ProductFixture::class, as: 'product', count: 3)
   ]
   public function testGetProductsCount(): void
   {
      $fixtures = DataFixtureStorageManager::getStorage();
      $product1 = $fixtures->get('product1');
      $product2 = $fixtures->get('product2');
      $product3 = $fixtures->get('product3');
   }
}

The generated fixtures will be assigned aliases product1, product2, and product3 (respectively).

Specifying the store scope for the data fixture

data-variant=info
data-slots=text
If you need to instruct the system to execute a data fixture in the scope of a specific store view, you can set the scope parameter value to the valid store view, site, or store group identifier.

In the following example, we create a new store with the store2 identifier and a product. Then, we create a guest cart under the store2 scope and add a created product to it.

class QuoteTest extends \PHPUnit\Framework\TestCase
{
    #[
        DataFixture(StoreFixture::class, as: 'store2'),
        DataFixture(ProductFixture::class, as: 'p'),
        DataFixture(GuestCartFixture::class, as: 'cart', scope: 'store2'),
        DataFixture(AddProductToCartFixture::class, ['cart_id' => '$cart.id$', 'product_id' => '$p.id$', 'qty' => 2]),
    ]
    public function testCollectTotals(): void
    {
    }
}

Test class and test method scopes

The DataFixture can be specified for a particular test case (method) or for an entire test class. The basic rules for fixture attributes at different levels are:

data-variant=info
data-slots=text
The integration testing framework interacts with a database to revert the applied fixtures.

Creating the fixture

Data Fixture is a PHP class that implements Magento\TestFramework\Fixture\DataFixtureInterface or Magento\TestFramework\Fixture\RevertibleDataFixtureInterface. Its main purpose is to seed the database with the data needed to run a test.

Principles

  1. Data Fixture class MUST implement Magento\TestFramework\Fixture\DataFixtureInterface or Magento\TestFramework\Fixture\RevertibleDataFixtureInterface if the data created by the fixture is revertible. For instance, a fixture that creates an entity (for example, product).
  2. Data Fixture class MUST be placed in the <ModuleName>/Test/Fixture folder of the corresponding module with namespace: <VendorName>\<ModuleName>\Test\Fixture (for example, Magento\Catalog\Test\Fixture).
  3. Data Fixture class SHOULD follow single responsibility principle.
  4. Data Fixture class MUST depend only on services from modules that are declared in the require section of its module's composer.json file.
  5. Data Fixture MUST NOT depend on another fixture.
  6. Data Fixture SHOULD be implemented using service APIs.
  7. Data Fixture SHOULD have dynamic default data to allow generating unique fixtures.
  8. Data Fixture MUST NOT handle input validation. Such validation should be handled by the service API.

Dynamic default data

In order to generate multiple fixtures of the same type without having to manually configure unique fields, you can use the placeholder %uniqid% in the default value of unique fields and Magento\TestFramework\Fixture\Data\ProcessorInterface to substitute the placeholder with a unique sequence.

Magento\Catalog\Test\Fixture\Product demonstrates the usage of %uniqid% (sku: simple-product%uniqid%) in the fixture default data.

In the following example, a unique sku is automatically generated for the first fixture (for example, simple-product61c10b2e86f991) and the second fixture (for example, simple-product61c10b2e86f992). The sequence is random and therefore unpredictable.

class ProductsListTest extends \PHPUnit\Framework\TestCase
{
     #[
        DataFixture(ProductFixture::class),
        DataFixture(ProductFixture::class)
     ]
    public function testGetProductsCount(): void
    {
    }
}

Comment block for data fixture

When creating a data fixture class, it is important to include a comprehensive comment block that provides a detailed description of the fixture’s usage and relevant use case scenarios. This documentation serves as the sole reference for other developers who use the fixture and is essential for maintaining its integrity. It is the responsibility of both the developer who creates the fixture and the developer who reviews the code to ensure that the documentation block is clear, accurate, and well-structured.

You must include the following in your documentation block:

Examples

Example 1:

/* 
*    Target Rule fixture creates a rule with conditions provided as an array.
*
*    Usage: Create target rule using array list. It will check SKU values is in (simple1,simple3) as condition. 
*
*    #[
*        DataFixture(
*            RuleFixture::class,
*            [
*                'conditions' => [
*                    [
*                        'attribute' => 'sku',
*                        'operator' => '()',
*                        'value' => 'simple1,simple3'
*                    ]
*                ]
*            ],
*            'rule'
*        )
*    ]
*/

Example 2: If the fixture can be used in different ways, provide a short description of each use case.

/* 
*    Target Rule fixture creates a rule with conditions provided as an array.
*
*    Usage-1: Create target rule using array list. It will check SKU values is in (simple1,simple3) as condition.
*
*    #[
*        DataFixture(
*            RuleFixture::class,
*            [
*                'conditions' => [
*                    [
*                        'attribute' => 'sku',
*                        'operator' => '()',
*                        'value' => 'simple1,simple3'
*                    ]
*                ]
*            ],
*            'rule'
*        )
*    ]
*    
*    Usage-2: Create target rule using associative array. It will check if sku=simple1 OR sku=simple3 as condition.
*    
*    #[
*        DataFixture(
*            RuleFixture::class,
*            [
*                'conditions' => [
*                    'aggregator' => 'any',
*                    'conditions' => [
*                        [
*                            'attribute' => 'sku',
*                            'value' => 'simple1'
*                        ],
*                        [
*                            'attribute' => 'sku',
*                            'value' => 'simple3'
*                        ]
*                    ],
*                ],
*            ],
*            'rule'
*        )
*    ]
*/

Decoupling fixtures

Fixtures must be written in the way that they only use one API to generate data. For example, the fixture that creates a product should only invoke the "Create Product" API and return the product created. This fixture should not add any extra logic beyond the "create product" API capabilities, such logic should be implemented in a separate fixture.

Examples

Example 1:

class QuoteTest extends \PHPUnit\Framework\TestCase
{

    #[
        DataFixture(ProductFixture::class, as: 'p'),
        DataFixture(GuestCartFixture::class, as: 'cart'),
        DataFixture(AddProductToCartFixture::class, ['cart_id' => '$cart.id$', 'product_id' => '$p.id$', 'qty' => 2]),
        DataFixture(SetBillingAddressFixture::class, ['cart_id' => '$cart.id$']),
        DataFixture(SetShippingAddressFixture::class, ['cart_id' => '$cart.id$']),
    ]
    public function testCollectTotals(): void
    {
    }
}

Example 2:

class PriceTest extends \PHPUnit\Framework\TestCase
{
    #[
        DataFixture(ProductFixture::class, ['sku' => 'simple1', 'price' => 10], 'p1'),
        DataFixture(ProductFixture::class, ['sku' => 'simple2', 'price' => 20], 'p2'),
        DataFixture(ProductFixture::class, ['sku' => 'simple3', 'price' => 30], 'p3'),
        DataFixture(BundleSelectionFixture::class, ['sku' => '$p1.sku$', 'price' => 10, 'price_type' => 0], 'link1'),
        DataFixture(BundleSelectionFixture::class, ['sku' => '$p2.sku$', 'price' => 25, 'price_type' => 1], 'link2'),
        DataFixture(BundleSelectionFixture::class, ['sku' => '$p3.sku$', 'price' => 25, 'price_type' => 0], 'link3'),
        DataFixture(BundleOptionFixture::class, ['product_links' => ['$link1$', '$link2$', '$link3$']], 'opt1'),
        DataFixture(
            BundleProductFixture::class,
            ['sku' => 'bundle1','price' => 50,'price_type' => 1,'_options' => ['$opt1$']],
            'bundle1'
        ),
    ]
    public function testBundleWithFixedPrice(): void
    {

    }
}

Fixture rollback

A fixture that contains database transactions only are reverted automatically. Otherwise, when a fixture creates files or performs any actions other than a database transaction, provide the corresponding rollback logic, in the revert method of the revertible data fixture. Rollbacks are run after reverting all the fixtures related to database transactions.

data-variant=info
data-slots=text
See the Magento\Catalog\Test\Fixture\Product fixture for an example.

Restrictions

Do not rely on and do not modify an application state from within a fixture. The application isolation attribute can reset the application state at any time.