How to work with Integration Tests in Magento 2
Integration testing is a type of testing where the real dependencies are used, to verify that the modules or components when integrated work individually as expected without any issues.
Prepare environment:
1. Use an existing or install a new magento project
2. Create a separate empty database for tests
3. Copy the file dev/tests/integration/etc/install-config-mysql.php.dist
to dev/tests/integration/etc/install-config-mysql.php and adjust the access data
to your newly created database. Attention: don’t change the backend-frontname setting!
return [ 'db-host' => 'YOUR DB HOST', 'db-user' => 'YOUR DB USER', 'db-password' => 'YOUR DB PASSWORD', 'db-name' => 'YOUR TEST DB NAME', 'db-prefix' => '', 'backend-frontname' => 'backend', 'admin-user' => \Magento\TestFramework\Bootstrap::ADMIN_NAME, 'admin-password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, 'admin-email' => \Magento\TestFramework\Bootstrap::ADMIN_EMAIL, 'admin-firstname' => \Magento\TestFramework\Bootstrap::ADMIN_FIRSTNAME, 'admin-lastname' => \Magento\TestFramework\Bootstrap::ADMIN_LASTNAME, ];
4. Copy the file dev/tests/integration/phpunit.xml.dist to
dev/tests/integration/phpunit.xml. If you don’t adjust the file after that,
you’ll be running the Magento 2 core testsuite which will take a few hours.
Change the part inside <testsuites>…</testsuites>
<testsuites> <!-- Memory tests run first to prevent influence of other tests on accuracy of memory measurements --> <!--<testsuite name="Memory Usage Tests"> <file>testsuite/Magento/MemoryUsageTest.php</file> </testsuite>--> <!--<testsuite name="Magento Integration Tests"> <directory suffix="Test.php">testsuite</directory> <exclude>testsuite/Magento/MemoryUsageTest.php</exclude> </testsuite>--> <testsuite name="VENDOR_NAME"> <directory suffix="Test.php">../../../app/code/VENDOR_NAME/*/Test/Integration</directory> </testsuite> </testsuites>
overwrite VENDOR_NAME to yours.
5. To run your tests use command: bin/magento dev:tests:run integration
TestCase class
Main class which is used for testing is PHPUnit\Framework\TestCase.
This class consists of many different methods to compare the actual result with the expected result.
Your test classes will be always extended from this class directly or through a parent class
Testing method names start from assert prefix.
For example: TestCase::assertStringContainsString
checks if some substring is included in the string, similar to PHP strpos function.
List of all methods you can view in TestCase class
Two important TestCase class methods are as follows:
1. TestCase::setUp – this method is used to create additional objects or data. Something similar to __construct, but without parameters. This method will be called automatically before your test will execute
2. TestCase::tearDown – this method is used to destroy/remove/reset all data in your class/db/filesystem. Something similar to __destruct. This method will be called automatically after your test execution finished not depends of test result
Test class structure and example
Recommendations:
1. Class name should have the suffix Test. Example: ProductPriceTest
2. Method which will be called for testing should be public, should have test
suffix in its name or @test string in method docblock
3. Try to use one class for one test instead of multiple test methods in one.
4. I prefer to use structures which should be clear for other developers.
So I create separate methods for each part of the test.
Methods to prepare data (given….), methods to execute functionality (when…) and
methods to check the result (then…). You can create a couple methods for each
test part. The structure can be: given…, when…, then1…. and then2…
Try to use structure for test method something like this:
public function someTestMethod() { $this->givenSomething(); $this->whenDoSomething(); $this->thenResultShouldBe(); }
In givenSomething method you can prepare your data or configuration or even request params.
In whenDoSomething method you execute your functionality which should be tested.
In thenResultShouldBe method you can check the execution result.
Full test example with comments:
use PHPUnit\Framework\TestCase; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\ObjectManagerInterface; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\Data\ProductInterfaceFactory; class ProductNameTest extends TestCase { private const PRODUCT_NAME = 'Test Product'; private ?ObjectManagerInterface $objectManager = null; private ?ProductInterfaceFactory $productFactory = null; private ?ProductInterface $product = null; protected function setUp() : void { $this->objectManager = Bootstrap::getObjectManager(); //here you can create some additional objects using $objectManager $this->productFactory = $this->objectManager->get(ProductInterfaceFactory::class); } /** * Main test method * * @test * @return void */ public function checkProductName(): void { $this->givenProduct(); $this->whenSetProductName(); $this->thenCheckProductName(); } private function givenProduct() { //here you can prepare some data. // Create product, login customer or add some core_config_data configuration $this->product = $this->productFactory->create(); } private function whenSetProductName() { //here you can execute your functionality to test $this->product->setName(self::PRODUCT_NAME); } private function thenCheckProductName() { //here you can check the result after your functionality was executed self::assertEquals(self::PRODUCT_NAME, $this->product->getName()); } protected function tearDown() : void { //here you can reset your parameters or delete created data from db or filesystem $this->product = null; } }
Explanation:
In this class I want to test if product name setter and getter works as expected.
1. Prepare additional class objects in setUp method to use them for testing
2. Create a checkProductName method which will be called when you run the tests command.
I added @test docblock to this method for declaring it as a test method
4. In test method I create a product object (method givenProduct)
5. After I set product name with test data (method whenSetProductName)
6. Then I get product name and compare it with previously set test data (method thenCheckProductName)
7. Reset product object in tearDown method