|
1 |
| -# Unit Testing Documentation |
| 1 | +# Unit Testing in Magento 2 |
2 | 2 |
|
3 | 3 | [TOC]
|
4 | 4 |
|
5 |
| -## Introduction |
| 5 | +## Overview |
6 | 6 |
|
7 |
| -Unit testing is an essential practice in software development that involves testing individual units or components of |
8 |
| -code to ensure their correctness. In this documentation, we will explore the fundamentals of unit testing, its benefits, |
9 |
| -and how to write effective unit tests for PHP and Magento 2. |
| 7 | +Unit testing is a critical part of software development. It involves testing individual units of code (such as functions |
| 8 | +or methods) to ensure they perform as expected. In Magento 2, PHPUnit is the testing framework used for writing and |
| 9 | +executing unit tests. |
10 | 10 |
|
11 |
| -## Benefits of Unit Testing |
| 11 | +Magento 2 has a solid structure for unit tests that separates them from other types of tests (like integration, |
| 12 | +functional, and performance tests). In a typical Magento 2 module, the unit tests are located in the `Test/Unit` |
| 13 | +directory of the module. |
12 | 14 |
|
13 |
| -Unit testing offers numerous benefits, including: |
| 15 | +## Setup and Configuration |
14 | 16 |
|
15 |
| -1. **Bug Prevention**: By testing individual units of code, potential bugs and errors can be identified and fixed early |
16 |
| - in the development process, reducing the overall cost and effort required. |
| 17 | +Before you begin, ensure you have PHPUnit installed. Magento 2 recommends using the latest stable PHPUnit version |
| 18 | +supported by your PHP installation. |
17 | 19 |
|
18 |
| -2. **Code Maintainability**: Unit tests act as a safety net when making changes to the codebase. They provide assurance |
19 |
| - that existing functionality will not break due to modifications, making it easier to refactor and improve code. |
| 20 | +You can install PHPUnit globally or as a part of your project via Composer. The following command installs PHPUnit |
| 21 | +globally: |
20 | 22 |
|
21 |
| -3. **Documentation**: Unit tests serve as living documentation, providing examples of how the code should be used and |
22 |
| - the expected behavior of its components. |
| 23 | +```bash |
| 24 | +composer global require phpunit/phpunit |
| 25 | +``` |
| 26 | + |
| 27 | +After installing PHPUnit, validate the installation by checking the version: |
23 | 28 |
|
24 |
| -4. **Rapid Feedback**: Unit tests enable quick feedback on code changes, as they can be executed independently and do |
25 |
| - not require the entire application to be running. |
| 29 | +```bash |
| 30 | +phpunit --version |
| 31 | +``` |
26 | 32 |
|
27 |
| -5. **Collaboration**: Unit tests facilitate collaboration among developers by providing a common understanding of code |
28 |
| - functionality and behavior. |
| 33 | +## Creating a Unit Test |
29 | 34 |
|
30 |
| -## Unit Testing in PHP |
| 35 | +In this section, we'll create a simple unit test for a hypothetical SampleModule in Magento 2. |
31 | 36 |
|
32 |
| -### PHPUnit |
| 37 | +1. Navigate to your module's directory and create a new Test/Unit directory if it doesn't exist. |
33 | 38 |
|
34 |
| -PHPUnit is a widely used unit testing framework for PHP. It provides a rich set of assertions, test runners, and other |
35 |
| -utilities to aid in writing effective unit tests. Let's look at some examples of how to write unit tests using PHPUnit. |
| 39 | +```bash |
| 40 | +mkdir -p app/code/Vendor/SampleModule/Test/Unit |
| 41 | +``` |
36 | 42 |
|
37 |
| -To begin, create a new test class, `MyClassTest`, that extends the `PHPUnit\Framework\TestCase` class: |
| 43 | +2. Inside the `Test/Unit` directory, create a new test class. In this case, we'll create `SampleTest.php`: |
38 | 44 |
|
39 | 45 | ```php
|
40 |
| -use PHPUnit\Framework\TestCase; |
| 46 | +<?php |
| 47 | +declare(strict_types=1); |
| 48 | + |
| 49 | +namespace Vendor\SampleModule\Test\Unit; |
41 | 50 |
|
42 |
| -class MyClassTest extends TestCase |
| 51 | +class SampleTest extends \PHPUnit\Framework\TestCase |
43 | 52 | {
|
44 |
| - // Test methods will be written here |
| 53 | + public function testSampleMethod(): void |
| 54 | + { |
| 55 | + $this->assertEquals(10, 5 + 5); |
| 56 | + } |
45 | 57 | }
|
46 | 58 | ```
|
47 | 59 |
|
48 |
| -#### Asserting Equality |
| 60 | +In this example, `testSampleMethod` is a simple test case that asserts whether `5 + 5` equals `10`. |
49 | 61 |
|
50 |
| -PHPUnit provides various assertion methods to validate expected outcomes. One common assertion is to check for equality |
51 |
| -between expected and actual values. For example: |
| 62 | +## Running Unit Tests |
52 | 63 |
|
53 |
| -```php |
54 |
| -public function testAddition() |
55 |
| -{ |
56 |
| - $result = 2 + 2; |
57 |
| - $this->assertEquals(4, $result); |
58 |
| -} |
| 64 | +To run the unit test, use the phpunit command from the root directory of your Magento 2 installation: |
| 65 | + |
| 66 | +```bash |
| 67 | +./vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/Vendor/SampleModule/Test/Unit |
59 | 68 | ```
|
60 | 69 |
|
61 |
| -In the above example, the `assertEquals` method checks whether the `$result` variable is equal to `4`. |
| 70 | +This command tells PHPUnit to use the configuration specified in phpunit.xml.dist and run the tests located |
| 71 | +in `app/code/Vendor/SampleModule/Test/Unit`. |
62 | 72 |
|
63 |
| -#### Mocking Dependencies |
| 73 | +To run an entire suite (i.e., all tests in a directory), specify the directory path: |
64 | 74 |
|
65 |
| -When testing code that has dependencies, it's often necessary to create mocks or stubs to isolate the unit being tested. |
66 |
| -PHPUnit provides a `getMock()` method to create mock objects. |
| 75 | +```bash |
| 76 | +./vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/Vendor/SampleModule/Test/Unit |
| 77 | +``` |
67 | 78 |
|
68 |
| -```php |
69 |
| -public function testSomethingWithMock() |
70 |
| -{ |
71 |
| - $dependencyMock = $this->getMockBuilder(DependencyClass::class) |
72 |
| - ->getMock(); |
| 79 | +The output should look something like this: |
73 | 80 |
|
74 |
| - // Use the mock object within the test |
75 |
| -} |
| 81 | +```bash |
| 82 | +PHPUnit X.X.X by Sebastian Bergmann and contributors. |
| 83 | + |
| 84 | +. 1 / 1 (100%) |
| 85 | + |
| 86 | +Time: X ms, Memory: X MB |
| 87 | + |
| 88 | +OK (1 test, 1 assertion) |
76 | 89 | ```
|
77 | 90 |
|
78 |
| -In the above example, the `getMockBuilder()` method creates a mock object of the `DependencyClass`. This allows the test |
79 |
| -to control the behavior of the dependency and focus solely on the unit being tested. |
| 91 | +## Run Unit Tests within PHPStorm |
| 92 | + |
| 93 | +To run tests within PHPStorm, follow these steps: |
| 94 | + |
| 95 | +Open the `Settings/Preferences` dialog (`Ctrl+Alt+S`), go to `Languages & Frameworks | PHP | Test Frameworks`. |
| 96 | +Click `+`, and choose `PHPUnit Local`. |
| 97 | +Configure the PHPUnit library and settings. |
| 98 | +After the configuration, go to your test, right-click on it and select Run `test-name`. |
| 99 | + |
| 100 | +## Write Testable Code |
| 101 | + |
| 102 | +Writing testable code involves designing your code in a way that makes it easier to isolate and test individual units. |
| 103 | +Some tips for writing testable code include: |
80 | 104 |
|
81 |
| -For more advanced mocking scenarios, PHPUnit also supports the use of mock libraries such as Mockery and Prophecy. |
| 105 | +**Single Responsibility**: Each method/class should have a single responsibility. It should do one thing and do it well. |
| 106 | +**Avoid Static Methods**: Static methods can't be mocked or stubbed, making them difficult to test. |
| 107 | +**Dependency Injection**: Using dependency injection helps make your code more flexible and easier to test. |
82 | 108 |
|
83 |
| -## Unit Testing in Magento 2 |
| 109 | +## Best Practices |
84 | 110 |
|
85 |
| -### Magento Testing Framework (MTF) |
| 111 | +**Use Descriptive Test Method Names**: The method name should describe what the test does. For example, |
| 112 | +`testUserIsCreatedWithValidInput` is descriptive and you can understand what the test does by looking at its name. |
| 113 | +**One Assertion Per Test Method**: Ideally, each test should only have one assertion. This makes the tests more readable |
| 114 | +and |
| 115 | +errors easier to pinpoint. |
| 116 | +**Don't Test Everything**: It's important to note that not all code needs to be tested. If it's already being tested |
| 117 | +elsewhere or it's part of the framework's functionality, there's usually no need to test it again. |
86 | 118 |
|
87 |
| -Magento 2 provides a dedicated testing framework called Magento Testing Framework (MTF) for writing unit tests. MTF |
88 |
| -extends PHPUnit and introduces additional features specific to Magento. |
89 | 119 |
|
90 |
| -To write unit tests in Magento 2, follow these steps: |
| 120 | +## Mocking |
91 | 121 |
|
92 |
| -1. Create a new test class that extends the `Magento\FunctionalTestingFramework\TestCase\TestCase` class: |
| 122 | +Mocking is a process used in unit testing when the unit of code has external dependencies. A mock object is a dummy |
| 123 | +implementation that simulates the behavior of a real object. |
93 | 124 |
|
94 | 125 | ```php
|
95 |
| -use Magento\FunctionalTestingFramework\TestCase\TestCase; |
| 126 | +<?php |
| 127 | +namespace Vendor\SampleModule\Test\Unit; |
96 | 128 |
|
97 |
| -class MyMagentoTest extends TestCase |
| 129 | +use PHPUnit\Framework\TestCase; |
| 130 | +use PHPUnit\Framework\MockObject\MockObject; |
| 131 | +use Vendor\SampleModule\Model\Sample; |
| 132 | + |
| 133 | +class SampleTest extends TestCase |
98 | 134 | {
|
99 |
| - // Test methods will be written here |
| 135 | + /** |
| 136 | + * @var MockObject |
| 137 | + */ |
| 138 | + private $sampleMock; |
| 139 | + |
| 140 | + protected function setUp(): void |
| 141 | + { |
| 142 | + $this->sampleMock = $this->getMockBuilder(Sample::class) |
| 143 | + ->disableOriginalConstructor() |
| 144 | + ->getMock(); |
| 145 | + } |
| 146 | + |
| 147 | + public function testSampleMethod() |
| 148 | + { |
| 149 | + $this->sampleMock->method('getNumber') |
| 150 | + ->willReturn(5); |
| 151 | + |
| 152 | + $this->assertEquals(10, $this->sampleMock->getNumber() + 5); |
| 153 | + } |
100 | 154 | }
|
101 | 155 | ```
|
102 | 156 |
|
103 |
| -2. Use the Magento testing APIs to interact with the Magento system and test the desired functionality. |
| 157 | +In this example, `Sample::class` is a dependency of the unit being tested. We create a mock for this dependency and |
| 158 | +define |
| 159 | +its behavior to return `5` when getNumber is called. |
| 160 | + |
| 161 | +## DocBlock Annotations |
| 162 | + |
| 163 | +DocBlocks or PHPDoc comments are used to provide metadata for your codebase. These comments can be parsed by tools to |
| 164 | +generate human-readable documentation. |
| 165 | + |
| 166 | +PHPUnit uses these annotations to control the behavior of your tests. Here are some commonly used annotations: |
| 167 | + |
| 168 | +- `@test`: This annotation can be used to mark a method as a test method. |
| 169 | +- `@dataProvider`: This annotation can be used to specify a method that returns data for a test. |
| 170 | +- `@depends`: This annotation can be used to specify that a test depends on another test. |
| 171 | +- `@group`: This annotation allows you to group tests together so you can run a set of tests separately from others. |
| 172 | + |
| 173 | +Example: |
104 | 174 |
|
105 | 175 | ```php
|
106 |
| -public function testMyCustomModule() |
| 176 | +/** |
| 177 | + * @test |
| 178 | + * @dataProvider additionProvider |
| 179 | + */ |
| 180 | +public function testAdd($a, $b, $expected) |
| 181 | +{ |
| 182 | + $this->assertEquals($expected, $a + $b); |
| 183 | +} |
| 184 | + |
| 185 | +public function additionProvider() |
107 | 186 | {
|
108 |
| - $this->loginAdminUser('admin', 'password'); |
109 |
| - $this->openAdminPage('my_custom_module'); |
110 |
| - $this->seePageTitle('My Custom Module'); |
111 |
| - // Additional assertions and interactions |
| 187 | + return [ |
| 188 | + [1, 2, 3], |
| 189 | + [0, 0, 0], |
| 190 | + [-1, -1, -2], |
| 191 | + ]; |
112 | 192 | }
|
113 | 193 | ```
|
114 | 194 |
|
115 |
| -In the above example, the test interacts with the Magento system by logging in as an admin user, opening an admin page, |
116 |
| -and asserting the expected page title. |
117 |
| - |
118 |
| -### Data Fixtures |
119 |
| - |
120 |
| -Magento 2 allows the creation of data fixtures to provide consistent test data for unit tests. Data fixtures are defined |
121 |
| -in XML files and can be used to set up test scenarios. |
122 |
| - |
123 |
| -```xml |
124 |
| -<testData> |
125 |
| - <fixture> |
126 |
| - <name>my_module_data</name> |
127 |
| - <data> |
128 |
| - <table_name> |
129 |
| - <row> |
130 |
| - <column1>value1</column1> |
131 |
| - <column2>value2</column2> |
132 |
| - </row> |
133 |
| - </table_name> |
134 |
| - </data> |
135 |
| - </fixture> |
136 |
| -</testData> |
| 195 | +In this example, `@test` marks `testAdd` as a test method, and `@dataProvider` specifies `additionProvider` as the |
| 196 | +method |
| 197 | +providing data for the `testAdd` test. |
| 198 | + |
| 199 | +This concludes our in-depth guide on unit testing in Magento 2. Remember that good tests can help you catch bugs early, |
| 200 | +make your codebase more maintainable, and save you, your team, and your users a lot of trouble down the line. Happy |
| 201 | +testing! |
| 202 | + |
| 203 | + |
| 204 | +## Code Coverage |
| 205 | + |
| 206 | +Code coverage is a measure that describes the degree to which the source code of a program is executed when a particular |
| 207 | +test suite runs. To generate a code coverage report, PHPUnit needs `Xdebug` or `pcov` installed. |
| 208 | + |
| 209 | +You can enable code coverage in the PHPUnit configuration file (phpunit.xml) or run the command: |
| 210 | + |
| 211 | +```bash |
| 212 | +./vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist --coverage-html coverage app/code/Vendor/SampleModule/Test/Unit |
137 | 213 | ```
|
138 | 214 |
|
139 |
| -In the above example, a fixture named `my_module_data` is defined, which inserts a row into the `table_name` table with |
140 |
| -specified column values. |
| 215 | +This command will generate an HTML report in the `coverage` directory. |
141 | 216 |
|
142 |
| -Fixtures can be used within a test using the `loadFixture()` method provided by MTF: |
| 217 | +## Run Unit Tests in CI |
143 | 218 |
|
144 |
| -```php |
145 |
| -public function testMyModuleWithFixture() |
146 |
| -{ |
147 |
| - $this->loadFixture('my_module_data'); |
148 |
| - // Additional test logic using the fixture data |
149 |
| -} |
| 219 | +### GitHub Actions |
| 220 | + |
| 221 | +In your `.github/workflows` directory, create a new file called `run-tests.yml` and populate it: |
| 222 | + |
| 223 | +```yaml |
| 224 | +name: Run PHPUnit Tests |
| 225 | + |
| 226 | +on: [ push, pull_request ] |
| 227 | + |
| 228 | +jobs: |
| 229 | + build: |
| 230 | + runs-on: ubuntu-latest |
| 231 | + |
| 232 | + steps: |
| 233 | + - uses: actions/checkout@v3 |
| 234 | + |
| 235 | + - name: Validate composer.json and composer.lock |
| 236 | + run: composer validate |
| 237 | + |
| 238 | + - name: Install Dependencies |
| 239 | + run: composer install --prefer-dist --no-progress --no-suggest |
| 240 | + |
| 241 | + - name: Run PHPUnit |
| 242 | + run: ./vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/Vendor/SampleModule/Test/Unit |
| 243 | +``` |
| 244 | +
|
| 245 | +### GitLab Pipelines |
| 246 | +
|
| 247 | +In your .gitlab-ci.yml, add: |
| 248 | +
|
| 249 | +```yaml |
| 250 | +stages: |
| 251 | + - test |
| 252 | + |
| 253 | +phpunit: |
| 254 | + stage: test |
| 255 | + image: php:latest |
| 256 | + script: |
| 257 | + - composer install |
| 258 | + - ./vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/Vendor/SampleModule/Test/Unit |
150 | 259 | ```
|
151 | 260 |
|
152 |
| -## Conclusion |
| 261 | +## Summary |
153 | 262 |
|
154 |
| -Unit testing plays a crucial role in software development, enabling developers to validate the correctness of individual |
155 |
| -units of code. By incorporating unit testing into your PHP and Magento 2 projects, you can increase code quality, reduce |
156 |
| -bugs, and improve maintainability. Use this documentation as a guide to get started with unit testing and leverage the |
157 |
| -benefits it offers. Happy testing! |
| 263 | +Unit testing is a vital part of the Magento 2 development process. It helps ensure that your code functions as expected, |
| 264 | +which can help reduce bugs and issues in the future. By using PHPUnit, Magento 2 developers can write and run unit tests |
| 265 | +to validate their code throughout the development process. |
0 commit comments