From c702b967c43d7a187a8837ef33ed8bb431b45e5b Mon Sep 17 00:00:00 2001 From: Buba Suma Date: Thu, 23 Apr 2020 13:03:44 -0500 Subject: [PATCH 1/6] Proposal for reusable and customizable data fixtures --- .../testing/integration/data-fixtures.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 design-documents/testing/integration/data-fixtures.md diff --git a/design-documents/testing/integration/data-fixtures.md b/design-documents/testing/integration/data-fixtures.md new file mode 100644 index 000000000..e9d884444 --- /dev/null +++ b/design-documents/testing/integration/data-fixtures.md @@ -0,0 +1,96 @@ +## Problem +When it comes to write a data fixture for your test case, the first thing you would do is to search for existing data fixture you can reuse in your test case. Most of the time you will find such data fixture that almost meets the requirements of your test case except that it's missing something very important to your test case. +Therefore you end up writing a new data fixture for your test case that is 99% a copy of existing one. +Here is a real example: + +- dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual.php +- dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_in_stock.php +- dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_out_of_stock.php + +All these 3 files create a virtual product except one marks the product as out of stock and the other assigns different quantity to the product. +So if you need a virtual product with available quantity 1, you will probably have to: + +- Write a new data fixture (copy-past) +- Reuse one of these data fixtures in a new data fixture file and edit the quantity there. +- Reuse one of these data fixtures in your test case and edit the quantity in the test directly. + +## Solution + +We could certainly redesign data fixtures to be object oriented. But if we just want to tackle this issue as simpler as we can, we could consist we could extend the format of data fixture annotation +to support a second parameter which will be injected to the data fixture file as following. +```php +/** + * @magentoDataFixture Magento/Catalog/_files/product_virtual.php, {"productData":{"stock_data": {"qty":1}}} + */ + +``` +The string after comma following the fixture file name is indeed the data that needs to be injected into the fixture file for customization. The format is well known JSON format that gives a flexible way to pass any type of data to the fixture (string, int, float and array). + +```php +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\DataObject; +use Magento\TestFramework\Helper\Bootstrap; + +$productData = $productData ?? []; +$defaultProductData = [ + 'id' => 21, + 'sku' => 'virtual-product', + 'name' => 'Virtual Product', + 'attribute_set_id' => 'sku', + 'website_ids' => 'sku', + 'price' => 'sku', + 'visibility' => Visibility::VISIBILITY_BOTH, + 'status' => Status::STATUS_ENABLED, + 'stock_data' => Status::STATUS_ENABLED, +]; + +$productData = array_merge($productData, $defaultProductData); +$objectManager = Bootstrap::getObjectManager(); +$productFactory = $objectManager->get(ProductInterfaceFactory::class); +/** @var ProductRepositoryInterface $productResource */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductInterface $product */ +$product = $productFactory->create(); +$product = data_object_hydrate_from_array($product, $productData); +$productResource->save($product); + +// this part of the code should be moved to a service. +// something similar to \Magento\Framework\Webapi\ServiceInputProcessor::convertValue but less strict. + +if (!function_exists('upper_came_case')) { + function upper_came_case(string $value): string + { + return str_replace('_', '', ucwords($value, '_')); + } +} + +if (!function_exists('data_object_hydrate_from_array')) { + function data_object_hydrate_from_array(DataObject $object, array $data): DataObject + { + foreach ($data as $key => $value) { + $camelCaseProperty = upper_came_case($key); + $setterName = 'set' . $camelCaseProperty; + $boolSetterName = 'setIs' . $camelCaseProperty; + if (method_exists($object, $setterName)) { + $object->$setterName($value); + unset($data[$key]); + } elseif (method_exists($object, $boolSetterName)) { + $object->$boolSetterName($value); + unset($data[$key]); + } + } + $object->addData($data); + return $object; + } +} +``` +**Pros** +- Reduces duplicate codes in data fixtures files +- Reduces the number of data fixtures files + +**Cons** +- Increases dock block size From 81d9d5ec84601b87d390e76bcfa045e170fe3840 Mon Sep 17 00:00:00 2001 From: Buba Suma Date: Mon, 27 Apr 2020 11:50:27 -0500 Subject: [PATCH 2/6] Proposal for reusable and customizable data fixtures --- .../testing/integration/data-fixtures.md | 138 +++++++++++++----- 1 file changed, 98 insertions(+), 40 deletions(-) diff --git a/design-documents/testing/integration/data-fixtures.md b/design-documents/testing/integration/data-fixtures.md index e9d884444..f1cd234d6 100644 --- a/design-documents/testing/integration/data-fixtures.md +++ b/design-documents/testing/integration/data-fixtures.md @@ -27,52 +27,30 @@ to support a second parameter which will be injected to the data fixture file as The string after comma following the fixture file name is indeed the data that needs to be injected into the fixture file for customization. The format is well known JSON format that gives a flexible way to pass any type of data to the fixture (string, int, float and array). ```php -use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Api\Data\ProductInterfaceFactory; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Catalog\Model\Product\Attribute\Source\Status; -use Magento\Catalog\Model\Product\Visibility; -use Magento\Framework\DataObject; +use Magento\TestFramework\Catalog\Model\ProductFixtureFactory; use Magento\TestFramework\Helper\Bootstrap; -$productData = $productData ?? []; -$defaultProductData = [ - 'id' => 21, - 'sku' => 'virtual-product', - 'name' => 'Virtual Product', - 'attribute_set_id' => 'sku', - 'website_ids' => 'sku', - 'price' => 'sku', - 'visibility' => Visibility::VISIBILITY_BOTH, - 'status' => Status::STATUS_ENABLED, - 'stock_data' => Status::STATUS_ENABLED, -]; - -$productData = array_merge($productData, $defaultProductData); $objectManager = Bootstrap::getObjectManager(); -$productFactory = $objectManager->get(ProductInterfaceFactory::class); -/** @var ProductRepositoryInterface $productResource */ -$productRepository = $objectManager->get(ProductRepositoryInterface::class); -/** @var ProductInterface $product */ -$product = $productFactory->create(); -$product = data_object_hydrate_from_array($product, $productData); -$productResource->save($product); - -// this part of the code should be moved to a service. -// something similar to \Magento\Framework\Webapi\ServiceInputProcessor::convertValue but less strict. - -if (!function_exists('upper_came_case')) { - function upper_came_case(string $value): string - { - return str_replace('_', '', ucwords($value, '_')); - } -} +$productFactory = $objectManager->get(ProductFixtureFactory::class); +$product = $productFactory->create($productData ?? []); +``` + +```php +namespace Magento\TestFramework; + +use Magento\Framework\DataObject; -if (!function_exists('data_object_hydrate_from_array')) { - function data_object_hydrate_from_array(DataObject $object, array $data): DataObject +class DataObjectHydrator +{ + /** + * @param DataObject $object + * @param array $data + * @return DataObject + */ + public function hydrate(DataObject $object, array $data): DataObject { foreach ($data as $key => $value) { - $camelCaseProperty = upper_came_case($key); + $camelCaseProperty = str_replace('_', '', ucwords($key, '_')); $setterName = 'set' . $camelCaseProperty; $boolSetterName = 'setIs' . $camelCaseProperty; if (method_exists($object, $setterName)) { @@ -88,6 +66,86 @@ if (!function_exists('data_object_hydrate_from_array')) { } } ``` + +```php +namespace Magento\TestFramework\Catalog\Model; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\TestFramework\DataObjectHydrator; + +class ProductFixtureFactory +{ + /** + * @var ProductInterfaceFactory + */ + private $factory; + /** + * @var ProductRepositoryInterface + */ + private $repository; + /** + * @var DataObjectHydrator + */ + private $dataObjectHydrator; + + /** + * @param ProductInterfaceFactory $factory + * @param ProductRepositoryInterface $repository + * @param DataObjectHydrator $dataObjectHydrator + */ + public function __construct( + ProductInterfaceFactory $factory, + ProductRepositoryInterface $repository, + DataObjectHydrator $dataObjectHydrator + ) { + $this->factory = $factory; + $this->repository = $repository; + $this->dataObjectHydrator = $dataObjectHydrator; + } + + /** + * @param array $data + * @return ProductInterface + */ + public function create(array $data): ProductInterface + { + $product = $this->factory->create(); + $product = $this->dataObjectHydrator->hydrate($product, array_merge($this->defaultData(), $data)); + $this->repository->save($product); + return $product; + } + + /** + * @return array + */ + private function defaultData(): array + { + return [ + 'id' => 21, + 'type_id' => Type::TYPE_VIRTUAL, + 'sku' => 'virtual-product', + 'name' => 'Virtual Product', + 'attribute_set_id' => 4, + 'tax_class_id' => 0, + 'website_ids' => [1], + 'price' => 10, + 'visibility' => Visibility::VISIBILITY_BOTH, + 'status' => Status::STATUS_ENABLED, + 'stock_data' => [ + 'qty' => 100, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ], + ]; + } +} +``` + **Pros** - Reduces duplicate codes in data fixtures files - Reduces the number of data fixtures files From ab892ec206bdbcdebfafc5497ddd8b1e12763a2e Mon Sep 17 00:00:00 2001 From: Buba Suma Date: Mon, 27 Apr 2020 13:14:27 -0500 Subject: [PATCH 3/6] Replace comma with space for consistency with other fixture annotations --- design-documents/testing/integration/data-fixtures.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/design-documents/testing/integration/data-fixtures.md b/design-documents/testing/integration/data-fixtures.md index f1cd234d6..d1443dfa9 100644 --- a/design-documents/testing/integration/data-fixtures.md +++ b/design-documents/testing/integration/data-fixtures.md @@ -20,11 +20,11 @@ We could certainly redesign data fixtures to be object oriented. But if we just to support a second parameter which will be injected to the data fixture file as following. ```php /** - * @magentoDataFixture Magento/Catalog/_files/product_virtual.php, {"productData":{"stock_data": {"qty":1}}} + * @magentoDataFixture Magento/Catalog/_files/product_virtual.php {"productData":{"stock_data": {"qty":1}}} */ ``` -The string after comma following the fixture file name is indeed the data that needs to be injected into the fixture file for customization. The format is well known JSON format that gives a flexible way to pass any type of data to the fixture (string, int, float and array). +The string following the fixture file name is indeed the data that needs to be injected into the fixture file for customization. The format is well known JSON format that gives a flexible way to pass any type of data to the fixture (string, int, float and array). ```php use Magento\TestFramework\Catalog\Model\ProductFixtureFactory; From 4371ad831696d8f598b2c4c7410af5b7dad92348 Mon Sep 17 00:00:00 2001 From: Buba Suma Date: Mon, 27 Apr 2020 13:22:46 -0500 Subject: [PATCH 4/6] Correct typo --- design-documents/testing/integration/data-fixtures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design-documents/testing/integration/data-fixtures.md b/design-documents/testing/integration/data-fixtures.md index d1443dfa9..b499fd394 100644 --- a/design-documents/testing/integration/data-fixtures.md +++ b/design-documents/testing/integration/data-fixtures.md @@ -16,7 +16,7 @@ So if you need a virtual product with available quantity 1, you will probably ha ## Solution -We could certainly redesign data fixtures to be object oriented. But if we just want to tackle this issue as simpler as we can, we could consist we could extend the format of data fixture annotation +We could certainly redesign data fixtures to be object oriented. But if we just want to tackle this issue as simpler as we can, we could extend the format of data fixture annotation to support a second parameter which will be injected to the data fixture file as following. ```php /** From c16ef3d3e11f1adc8760aa3c45715e2ea02a8502 Mon Sep 17 00:00:00 2001 From: Buba Suma Date: Thu, 21 May 2020 16:00:34 -0500 Subject: [PATCH 5/6] Replace the json format with a new format that allows resolving dependencies --- .../testing/integration/data-fixtures.md | 214 ++++++++++++++---- 1 file changed, 174 insertions(+), 40 deletions(-) diff --git a/design-documents/testing/integration/data-fixtures.md b/design-documents/testing/integration/data-fixtures.md index b499fd394..faa9c1ae0 100644 --- a/design-documents/testing/integration/data-fixtures.md +++ b/design-documents/testing/integration/data-fixtures.md @@ -1,6 +1,6 @@ ## Problem When it comes to write a data fixture for your test case, the first thing you would do is to search for existing data fixture you can reuse in your test case. Most of the time you will find such data fixture that almost meets the requirements of your test case except that it's missing something very important to your test case. -Therefore you end up writing a new data fixture for your test case that is 99% a copy of existing one. +Therefore, you end up writing a new data fixture for your test case that is 99% a copy of existing one. Here is a real example: - dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual.php @@ -16,59 +16,100 @@ So if you need a virtual product with available quantity 1, you will probably ha ## Solution -We could certainly redesign data fixtures to be object oriented. But if we just want to tackle this issue as simpler as we can, we could extend the format of data fixture annotation -to support a second parameter which will be injected to the data fixture file as following. +Extend the format of data fixture annotation as following: + ```php -/** - * @magentoDataFixture Magento/Catalog/_files/product_virtual.php {"productData":{"stock_data": {"qty":1}}} - */ +class QuoteTest extends \PHPUnit\Framework\TestCase +{ + /** + * @magentoDataFixture Magento/Catalog/_files/product_virtual.php as virtualProduct + * @magentoDataFixture Magento/Customer/_files/customer.php as customer + * @magentoDataFixture Magento/Customer/_files/customer_address.php as customerAddress require customer + * @magentoDataFixture Magento/Checkout/_files/quote.php as quote require customer, virtualProduct + */ + public function testCollectTotals(): void + { + } + public function collectTotalsDataFixture(): array + { + return [ + 'virtualProduct' => [ + 'special_price' => 15 + ], + 'customerAddress' => [ + 'telephone' => '+15129000201', + '@customer' => 'customer', + ], + 'quote' => [ + 'is_multi_shipping' => 1, + '@customer' => 'customer', + '@products[]' => [ + 'virtualProduct' => [ + 'qty' => 2 + ] + ], + ], + ]; + } +} ``` -The string following the fixture file name is indeed the data that needs to be injected into the fixture file for customization. The format is well known JSON format that gives a flexible way to pass any type of data to the fixture (string, int, float and array). +With the `as` and `require` keywords, we'll be able to inject data and resolve dependencies between fixtures. +The `as` keyword is used for aliasing a fixture and the `require` keyword is used to declare fixtures dependencies on each other using fixtures aliases. + +The method `collectTotalsDataFixture` deducted from the test name with suffix `DataFixture` is the data provider for all fixtures defined for test case. Test class fixture data provider method would be `dataFixture`. The data fixture provider should return an associative array with fixtures aliases as key and data to overwrite the corresponding fixture data. +The data fixture provider uses `@` before attribute name to resolve dependencies. The `@` prefixed attribute value must be an associative array which keys are dependent fixtures aliases and values are an array containing the relationship data. + +In the example above, `Magento/Catalog/_files/product_virtual.php`, `Magento/Customer/_files/customer.php`, `Magento/Customer/_files/customer_address.php` and `Magento/Checkout/_files/quote.php` are respectively aliased as _virtualProduct_, _customer_, _customerAddress_ and _quote_. +`Magento/Customer/_files/customer_address.php` requires _customer_ (`Magento/Customer/_files/customer.php`) and `Magento/Checkout/_files/quote.php` requires _virtualProduct_ (`Magento/Catalog/_files/product_virtual.php`) and _customer_ (`Magento/Customer/_files/customer.php`) fixtures. +The data fixture provider overwrites the fixtures _virtualProduct_, _customerAddress_ and _quote_ data as well as resolves fixtures dependencies using the `@` keyword. ```php -use Magento\TestFramework\Catalog\Model\ProductFixtureFactory; +#Magento/Catalog/_files/product_virtual.php +use Magento\Catalog\Fixtures\ProductFixture; use Magento\TestFramework\Helper\Bootstrap; $objectManager = Bootstrap::getObjectManager(); -$productFactory = $objectManager->get(ProductFixtureFactory::class); -$product = $productFactory->create($productData ?? []); +$productFixture = $objectManager->get(ProductFixture::class); +$product = $productFixture->create($data ?? []); +return $product; ``` ```php -namespace Magento\TestFramework; +#Magento/Customer/_files/customer.php +use Magento\Customer\Fixtures\CustomerFixture; +use Magento\TestFramework\Helper\Bootstrap; -use Magento\Framework\DataObject; +$objectManager = Bootstrap::getObjectManager(); +$customerFixture = $objectManager->get(CustomerFixture::class); +$customer = $customerFixture->create($data ?? []); +return $customer; +``` -class DataObjectHydrator -{ - /** - * @param DataObject $object - * @param array $data - * @return DataObject - */ - public function hydrate(DataObject $object, array $data): DataObject - { - foreach ($data as $key => $value) { - $camelCaseProperty = str_replace('_', '', ucwords($key, '_')); - $setterName = 'set' . $camelCaseProperty; - $boolSetterName = 'setIs' . $camelCaseProperty; - if (method_exists($object, $setterName)) { - $object->$setterName($value); - unset($data[$key]); - } elseif (method_exists($object, $boolSetterName)) { - $object->$boolSetterName($value); - unset($data[$key]); - } - } - $object->addData($data); - return $object; - } -} +```php +#Magento/Customer/_files/customer_address.php +use Magento\Customer\Fixtures\CustomerAddressFixture; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +$customerAddressFixture = $objectManager->get(CustomerAddressFixture::class); +$customerAddress = $customerAddressFixture->create(['customer' => $customer, 'data' => $data ?? []]); +return $customerAddress; ``` ```php -namespace Magento\TestFramework\Catalog\Model; +#Magento/Checkout/_files/quote.php +use Magento\Checkout\Fixtures\QuoteFixture; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +$quoteFixture = $objectManager->get(QuoteFixture::class); +$quote = $quoteFixture->create(['customer' => $customer, 'products' => $products, 'data' => $data ?? []]); +return $quote; +``` + +```php +namespace Magento\Catalog\Fixtures; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\Data\ProductInterfaceFactory; @@ -77,8 +118,9 @@ use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\Product\Visibility; use Magento\TestFramework\DataObjectHydrator; +use Magento\TestFramework\FixtureInterface; -class ProductFixtureFactory +class ProductFixture implements FixtureInterface { /** * @var ProductInterfaceFactory @@ -112,7 +154,7 @@ class ProductFixtureFactory * @param array $data * @return ProductInterface */ - public function create(array $data): ProductInterface + public function create(array $data) { $product = $this->factory->create(); $product = $this->dataObjectHydrator->hydrate($product, array_merge($this->defaultData(), $data)); @@ -145,6 +187,98 @@ class ProductFixtureFactory } } ``` +With the examples above, one can notice that there is absolutely no need for fixtures file anymore. Instead, we can simply use the fixture class in the data fixture annotation as following: + +```php +class QuoteTest extends \PHPUnit\Framework\TestCase +{ + /** + * @magentoDataFixture \Magento\Catalog\Fixtures\ProductFixture as virtualProduct + * @magentoDataFixture \Magento\Customer\Fixtures\CustomerFixture as customer + * @magentoDataFixture \Magento\Customer\Fixtures\CustomerAddressFixture as customerAddress require customer + * @magentoDataFixture \Magento\Checkout\Fixtures\QuoteFixture as quote require customer, virtualProduct + */ + public function testCollectTotals(): void + { + } + + public function collectTotalsDataFixture(): array + { + return []; + } +} +``` +You can create as many instances of a fixture as you wish for your test case: + +```php +class ProductsList extends \PHPUnit\Framework\TestCase +{ + /** + * @magentoDataFixture \Magento\Catalog\Fixtures\ProductFixture as product1 + * @magentoDataFixture \Magento\Catalog\Fixtures\ProductFixture as product2 + * @magentoDataFixture \Magento\Catalog\Fixtures\ProductFixture as product3 + */ + public function testGetProductsCount(): void + { + } + + public function getProductsCountDataFixture(): array + { + return [ + 'product1' => [ + 'sku' => 'simple1' + ], + 'product2' => [ + 'sku' => 'simple2' + ], + 'product3' => [ + 'sku' => 'simple3', + 'status' => Status::STATUS_DISABLED, + ], + ]; + } +} +``` + +**Integration test extensibility** + +```xml + + + + + + + + + + 15.0 + + + 1 + + + + + + + + + 1 + + + + 2 + + + + + + + + +``` + **Pros** - Reduces duplicate codes in data fixtures files From d7a7ee70dea8a682b715945c90e8a47900010a0f Mon Sep 17 00:00:00 2001 From: Buba Suma Date: Mon, 1 Jun 2020 10:48:21 -0500 Subject: [PATCH 6/6] Fix grammar --- .../testing/integration/data-fixtures.md | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/design-documents/testing/integration/data-fixtures.md b/design-documents/testing/integration/data-fixtures.md index faa9c1ae0..4fef17596 100644 --- a/design-documents/testing/integration/data-fixtures.md +++ b/design-documents/testing/integration/data-fixtures.md @@ -7,12 +7,8 @@ Here is a real example: - dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_in_stock.php - dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_out_of_stock.php -All these 3 files create a virtual product except one marks the product as out of stock and the other assigns different quantity to the product. -So if you need a virtual product with available quantity 1, you will probably have to: - -- Write a new data fixture (copy-past) -- Reuse one of these data fixtures in a new data fixture file and edit the quantity there. -- Reuse one of these data fixtures in your test case and edit the quantity in the test directly. +All these 3 files create a virtual product except that one marks the product as out of stock and the other assigns different quantity to the product. +So if you need a virtual product with available quantity 1, you will probably have to create a new data fixture. ## Solution @@ -55,14 +51,14 @@ class QuoteTest extends \PHPUnit\Framework\TestCase } ``` With the `as` and `require` keywords, we'll be able to inject data and resolve dependencies between fixtures. -The `as` keyword is used for aliasing a fixture and the `require` keyword is used to declare fixtures dependencies on each other using fixtures aliases. +The `as` keyword is used for aliasing fixtures and the `require` keyword is used to declare fixtures dependencies on each other using fixtures aliases. + +The method `collectTotalsDataFixture()` deducted from the test name with suffix `DataFixture` is the data provider for all fixtures defined for test case. Test case class fixture data provider method would be simply `dataFixture()`. The data fixture provider should return an associative array with fixtures aliases as key and data to overwrite the corresponding fixture data. +The data fixture provider uses `@` before attribute name to resolve dependencies. The `@` prefixed attribute value can be a _string_ or an associative array which keys are depending fixtures aliases and values are an array containing the relationship data. -The method `collectTotalsDataFixture` deducted from the test name with suffix `DataFixture` is the data provider for all fixtures defined for test case. Test class fixture data provider method would be `dataFixture`. The data fixture provider should return an associative array with fixtures aliases as key and data to overwrite the corresponding fixture data. -The data fixture provider uses `@` before attribute name to resolve dependencies. The `@` prefixed attribute value must be an associative array which keys are dependent fixtures aliases and values are an array containing the relationship data. +In the example above, the data fixture provider overwrites the fixtures _virtualProduct_, _customerAddress_ and _quote_ data as well as resolves fixtures dependencies using the `@` keyword. -In the example above, `Magento/Catalog/_files/product_virtual.php`, `Magento/Customer/_files/customer.php`, `Magento/Customer/_files/customer_address.php` and `Magento/Checkout/_files/quote.php` are respectively aliased as _virtualProduct_, _customer_, _customerAddress_ and _quote_. -`Magento/Customer/_files/customer_address.php` requires _customer_ (`Magento/Customer/_files/customer.php`) and `Magento/Checkout/_files/quote.php` requires _virtualProduct_ (`Magento/Catalog/_files/product_virtual.php`) and _customer_ (`Magento/Customer/_files/customer.php`) fixtures. -The data fixture provider overwrites the fixtures _virtualProduct_, _customerAddress_ and _quote_ data as well as resolves fixtures dependencies using the `@` keyword. +**Examples** ```php #Magento/Catalog/_files/product_virtual.php @@ -71,7 +67,7 @@ use Magento\TestFramework\Helper\Bootstrap; $objectManager = Bootstrap::getObjectManager(); $productFixture = $objectManager->get(ProductFixture::class); -$product = $productFixture->create($data ?? []); +$product = $productFixture->create(['data' => $data ?? []]); return $product; ``` @@ -82,7 +78,7 @@ use Magento\TestFramework\Helper\Bootstrap; $objectManager = Bootstrap::getObjectManager(); $customerFixture = $objectManager->get(CustomerFixture::class); -$customer = $customerFixture->create($data ?? []); +$customer = $customerFixture->create(['data' => $data ?? []]); return $customer; ``` @@ -93,7 +89,7 @@ use Magento\TestFramework\Helper\Bootstrap; $objectManager = Bootstrap::getObjectManager(); $customerAddressFixture = $objectManager->get(CustomerAddressFixture::class); -$customerAddress = $customerAddressFixture->create(['customer' => $customer, 'data' => $data ?? []]); +$customerAddress = $customerAddressFixture->create(['data' => $data ?? [], 'customer' => $customer]); return $customerAddress; ``` @@ -104,7 +100,7 @@ use Magento\TestFramework\Helper\Bootstrap; $objectManager = Bootstrap::getObjectManager(); $quoteFixture = $objectManager->get(QuoteFixture::class); -$quote = $quoteFixture->create(['customer' => $customer, 'products' => $products, 'data' => $data ?? []]); +$quote = $quoteFixture->create(['data' => $data ?? [], 'customer' => $customer, 'products' => $products]); return $quote; ``` @@ -187,7 +183,9 @@ class ProductFixture implements FixtureInterface } } ``` -With the examples above, one can notice that there is absolutely no need for fixtures file anymore. Instead, we can simply use the fixture class in the data fixture annotation as following: +**Class based fixtures** + +In the examples above, one can notice that there is absolutely no need for fixtures file anymore. Instead, we can simply and directly use fixture classes in the data fixture annotation as following: ```php class QuoteTest extends \PHPUnit\Framework\TestCase @@ -208,7 +206,7 @@ class QuoteTest extends \PHPUnit\Framework\TestCase } } ``` -You can create as many instances of a fixture as you wish for your test case: +One can create as many instances of a fixture as you wish for your test case: ```php class ProductsList extends \PHPUnit\Framework\TestCase