Working with data fixtures
Data fixtures are reusable components that set up test data in your database before running integration tests. They help you create a consistent, predictable test environment by populating the database with entities like products, customers, orders, categories, and more.
Think of data fixtures as the "arrange" phase of your test--they prepare the data your test needs to verify specific functionality.
Modern vs legacy approaches
Adobe Commerce and Magento Open Source support multiple ways to work with data fixtures, but not all are equally recommended.
Legacy fixtures (file-based fixtures like Magento/Catalog/_files/product_simple.php) are deprecated. New legacy fixtures cannot be created. Use parameterized data fixtures instead for better maintainability, reusability, and type safety.
Parameterized data fixtures are the recommended approach. They require PHP attributes (#[DataFixture()]), which offer superior type safety, better IDE support, improved readability, and powerful parameterization capabilities. DocBlock annotations (@magentoDataFixture) only work with legacy file-based fixtures.
Comparison
| Approach | Status | Can Parameterize | Syntax Example |
|---|---|---|---|
Legacy fixtures (file-based) | Deprecated | ❌ No | @magentoDataFixture Magento/Catalog/_files/product.php |
Parameterized data fixtures (with PHP Attributes) | ✅ Recommended | ✅ Yes | #[DataFixture(ProductFixture::class, ['price' => 10], 'product')] |
Parameterized data fixtures cannot be used with DocBlock annotations (@magentoDataFixture). They require PHP Attributes (#[DataFixture()]).
Why avoid legacy fixtures?
- No parameterization - Each variation requires a separate file
- Hard to maintain - Changes require modifying PHP scripts scattered across the codebase
- Poor discoverability - Difficult to find what data a fixture creates
- No type safety - Parameters and return values are not typed
- Coupling - Tests become tightly coupled to specific fixture files
Types of data fixtures
This section covers the two main types of data fixtures: legacy file-based fixtures and modern parameterized data fixtures.
Parameterized data fixtures (Recommended)
These are PHP classes that implement DataFixtureInterface or RevertibleDataFixtureInterface. They support powerful parameterization and require PHP Attributes (#[DataFixture()]).
Parameterized data fixtures cannot be used with DocBlock annotations. Only PHP Attributes support parameterization.
The power of modern parameterized data fixtures lies in their ability to be customized as you can configure the data they create without writing new fixture classes.
For detailed information on basic parameterization, aliases, store scope, and the count parameter, see DataFixture Attribute.
Basic example:
Copied to your clipboard// Only override values that matter for your test. SKU is auto-generated with a unique value#[DataFixture(ProductFixture::class, ['price' => 10.00], 'product')]public function testProductExists(): void{$fixtures = DataFixtureStorageManager::getStorage();$product = $fixtures->get('product');}
Fixture references - building complex scenarios:
You can reference data from one fixture in another using the $alias.property$ syntax. This allows you to build complex, interconnected test data scenarios:
Copied to your clipboard#[DataFixture(CustomerFixture::class, as: 'customer'),DataFixture(User::class, as: 'user'),DataFixture(Company::class,['sales_representative_id' => '$user.id$', 'super_user_id' => '$customer.id$'],'company')]
Legacy fixtures (Deprecated)
Legacy fixtures are file-based PHP scripts (stored in _files/ directories) that directly execute database operations. They cannot be parameterized and are now deprecated.
New legacy fixtures cannot be created. Existing legacy fixtures should be migrated to parameterized data fixtures.
Example with DocBlock annotation:
Copied to your clipboard/*** @magentoDataFixture Magento/Catalog/_files/product_simple.php*/public function testProductExists(): void{// Test logic here - but you're stuck with whatever data the file creates}
Example of a legacy fixture file: Magento/Catalog/_files/product_simple.php
Copied to your clipboard<?phpuse Magento\Catalog\Api\ProductRepositoryInterface;use Magento\Catalog\Model\Product;use Magento\TestFramework\Helper\Bootstrap;$product = Bootstrap::getObjectManager()->create(Product::class);$product->setTypeId('simple')->setAttributeSetId(4)->setName('Simple Product')->setSku('simple') // Fixed SKU - can't customize->setPrice(10) // Fixed price - can't customize->setVisibility(4)->setStatus(1);Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class)->save($product);
Problems: To create a product with a different SKU or price, you need to create an entirely new fixture file!
Migrating legacy fixtures to modern
If you have tests using legacy fixtures, you should migrate them to parameterized data fixtures with PHP Attributes. New legacy fixtures cannot be created.
Before (Legacy fixtures)
Copied to your clipboard/*** @magentoDataFixture Magento/Catalog/_files/product_simple.php*/public function testProductPrice(): void{$product = $this->productRepository->get('simple'); // Fixed SKU$this->assertEquals(10, $product->getPrice()); // Fixed price}
After (Parameterized data fixtures with PHP Attributes)
Copied to your clipboard#[DataFixture(ProductFixture::class, ['price' => 10], 'product')]public function testProductPrice(): void{$fixtures = DataFixtureStorageManager::getStorage();$product = $fixtures->get('product');$this->assertEquals(10, $product->getPrice());}
Retrieving fixture data
Always use DataFixtureStorageManager to retrieve fixture data created by fixtures:
Copied to your clipboarduse Magento\TestFramework\Fixture\DataFixtureStorageManager;#[DataFixture(ProductFixture::class, ['price' => 25.00], 'test_product')]public function testProduct(): void{$product = DataFixtureStorageManager::getStorage()->get('test_product');$this->assertEquals(25.00, $product->getPrice());}
Complex example: Inventory with multiple sources
Example showing fixture composition and references:
Copied to your clipboarduse Magento\TestFramework\Fixture\DataFixtureStorageManager;#[DataFixture(SourceFixture::class, ['source_code' => 'test_source'], 'source'),DataFixture(StockFixture::class, as: 'stock'),DataFixture(StockSourceLinksFixture::class,[['stock_id' => '$stock.stock_id$', 'source_code' => '$source.source_code$']]),DataFixture(StockSalesChannelsFixture::class,['stock_id' => '$stock.stock_id$', 'sales_channels' => ['base']]),DataFixture(ProductFixture::class, as: 'p1'),DataFixture(ProductFixture::class, as: 'p2'),DataFixture(SourceItemsFixture::class,[['sku' => '$p1.sku$', 'source_code' => '$source.source_code$', 'quantity' => 10, 'status' => 1],['sku' => '$p2.sku$', 'source_code' => '$source.source_code$', 'quantity' => 20, 'status' => 1],]),DataFixture(Indexer::class, as: 'indexer')]public function testInventoryConfiguration(): void{$stock = DataFixtureStorageManager::getStorage()->get('stock');$product1 = DataFixtureStorageManager::getStorage()->get('p1');// Test inventory logic}
This example demonstrates:
- Creating related fixtures (source, stock, products)
- Linking fixtures together using
$alias.property$references - Passing arrays of data with references
- Building complex test scenarios with multiple dependencies
Best practices
Follow these best practices when working with data fixtures.
Do not create legacy fixtures
New legacy fixtures cannot be created. Do not create new tests using legacy fixtures (e.g., Magento/Catalog/_files/product_simple.php). Always use parameterized data fixtures instead. Existing legacy fixtures should be migrated.
Bad - Cannot create new:
Copied to your clipboard/*** @magentoDataFixture Magento/Catalog/_files/product_simple.php*/
Good - Use parameterized data fixtures with PHP Attributes:
Copied to your clipboard#[DataFixture(ProductFixture::class, ['price' => 10])]
Only override fixture values that are necessary for your test. Unique fields like SKU are dynamically generated and should not be overridden unless specifically required by the test scenario.
Use PHP Attributes for parameterized data fixtures
Parameterized data fixtures require PHP Attributes.
Keep fixtures focused
Each fixture should do one thing. Use multiple fixtures and compose them rather than creating complex all-in-one fixtures.
Use fixture aliases for references
Always use the as parameter when you need to reference fixture data in other fixtures:
Copied to your clipboard#[DataFixture(CategoryFixture::class, as: 'category'),DataFixture(ProductFixture::class, ['category_ids' => ['$category.id$']])]
Leverage fixture defaults
Most fixtures have sensible defaults with dynamic unique values. Only override values that matter for your specific test scenario.
Don't override unique fields unnecessarily:
Copied to your clipboard// Bad - overriding SKU when not needed#[DataFixture(ProductFixture::class, ['sku' => 'my-sku', 'price' => 100], 'product')]// Good - let SKU be auto-generated and reference it when needed#[DataFixture(ProductFixture::class, ['price' => 100], 'product'),DataFixture(CatalogRuleFixture::class,['is_active' => false,'conditions' => [['attribute' => 'sku', 'value' => '$product.sku$']]],'rule')]public function testCatalogRule(): void{$product = DataFixtureStorageManager::getStorage()->get('product');$rule = DataFixtureStorageManager::getStorage()->get('rule');// Access auto-generated SKU$sku = $product->getSku();}
You can reference auto-generated values using $alias.property$ syntax without overriding them.
Understand isolation behavior
Database isolation is enabled by default when using data fixtures. Application isolation has significant performance implications and should only be used when necessary (e.g., when modifying application state like sessions). See AppIsolation and DbIsolation for details.
Fixture cleanup is automatic
Fixtures that implement RevertibleDataFixtureInterface are automatically cleaned up. This is critical to prevent test pollution where data from one test affects another test.
Proper data cleanup is essential. Tests run in groups (test suites), and leftover data from one test can cause unexpected failures in subsequent tests. Always ensure your fixtures properly implement the revert() method.
Rely only on explicitly configured fixtures
Your tests should depend on fixtures created before the test runs. However, only rely on things you've explicitly configured in your test's fixture declarations, not on implicit side effects or fixtures from other tests.
Finding available fixtures
Most Commerce modules include test fixtures in their Test/Fixture directory:
Magento/Catalog/Test/Fixture/Product.phpMagento/Customer/Test/Fixture/Customer.phpMagento/Sales/Test/Fixture/Order.phpMagento/Cms/Test/Fixture/Page.php
Check these directories in the Adobe Commerce or Magento Open Source codebase to discover available fixtures and their parameters.
Creating custom fixtures
If you need custom fixtures, create a class that implements DataFixtureInterface or RevertibleDataFixtureInterface.
For detailed guidance on creating custom data fixtures, see the official Magento DevBlog article: How to Create Data Fixtures (Part 2/2).
Basic structure
Copied to your clipboard<?phpnamespace Vendor\Module\Test\Fixture;use Magento\TestFramework\Fixture\DataFixtureInterface;use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;class CustomFixture implements RevertibleDataFixtureInterface{public function __construct(private YourDependency $dependency) {}public function apply(array $data = []): ?DataFixtureStorageInterface{// Create your entity$entity = $this->dependency->create($data);return new DataFixtureStorage($entity);}public function revert(DataFixtureInterface $dataFixture): void{// Clean up the entity$entity = $dataFixture->get();$this->dependency->delete($entity);}}
When to use RevertibleDataFixtureInterface
Use RevertibleDataFixtureInterface when the data fixture creates data that needs to be manually removed after the test. Examples:
- Product data
- Category data
- Customer data
- Any persistent entity
Use DataFixtureInterface when the data is automatically removed with related data. Examples:
- Adding a shipping address to a cart (removed when cart is deleted)
- Assigning a product to a category (removed when product is deleted)
Learn more
Refer to the following resources for more information on data fixtures.
Official Documentation
- DocBlock Annotations - Complete annotation reference
- PHP Attributes - Complete attributes reference
- DataFixture Attribute - Detailed DataFixture documentation
- @magentoDataFixture Annotation - Legacy annotation documentation
DevBlog Articles
- How to Use and Create Data Fixture in Integration and API Functional Tests (Part 1/2) - Official guide on using data fixtures with real examples
- How to Use and Create Data Fixture in Integration and API Functional Tests (Part 2/2) - Official guide on creating custom data fixtures
