Magento 2 and Automated Testing: Reducing Bugs in Custom Modules

Why Automated Testing Matters in Magento 2
Let’s be honest—custom Magento 2 modules can be buggy. Even small changes can break things unexpectedly. That’s where automated testing comes in. Instead of manually clicking through your store every time you make an update, automated tests do the heavy lifting for you. They catch issues before they reach production, saving you time, money, and headaches.
Magento 2 supports three main types of automated tests:
- Unit Tests – Test individual PHP classes in isolation.
- Integration Tests – Check how different components work together.
- Functional Tests – Simulate real user interactions (like clicking buttons).
Setting Up PHPUnit for Unit Testing
First, make sure PHPUnit is installed. If you’re using Composer (which you should be), run:
composer require --dev phpunit/phpunit
Now, let’s say you have a simple helper class in your custom module at app/code/Vendor/Module/Helper/Data.php
:
<?php
namespace Vendor\Module\Helper;
class Data
{
public function addNumbers($a, $b)
{
return $a + $b;
}
}
To test this, create a test file at app/code/Vendor/Module/Test/Unit/Helper/DataTest.php
:
<?php
namespace Vendor\Module\Test\Unit\Helper;
use PHPUnit\Framework\TestCase;
use Vendor\Module\Helper\Data;
class DataTest extends TestCase
{
public function testAddNumbers()
{
$helper = new Data();
$this->assertEquals(5, $helper->addNumbers(2, 3));
}
}
Run the test with:
vendor/bin/phpunit app/code/Vendor/Module/Test/Unit/Helper/DataTest.php
If everything’s correct, you’ll see a green "OK" message. If not, PHPUnit tells you exactly what went wrong.
Integration Testing in Magento 2
Integration tests are where Magento’s real power shines. They test how your code interacts with the framework. Let’s test a basic model.
Assume you have a model at app/code/Vendor/Module/Model/Example.php
:
<?php
namespace Vendor\Module\Model;
use Magento\Framework\Model\AbstractModel;
class Example extends AbstractModel
{
protected function _construct()
{
$this->_init('Vendor\Module\Model\ResourceModel\Example');
}
}
Create an integration test at app/code/Vendor/Module/Test/Integration/Model/ExampleTest.php
:
<?php
namespace Vendor\Module\Test\Integration\Model;
use Magento\TestFramework\ObjectManager;
use PHPUnit\Framework\TestCase;
use Vendor\Module\Model\Example;
class ExampleTest extends TestCase
{
public function testModelLoad()
{
$objectManager = ObjectManager::getInstance();
$model = $objectManager->create(Example::class);
$model->setData(['name' => 'Test']);
$model->save();
$loadedModel = $objectManager->create(Example::class);
$loadedModel->load($model->getId());
$this->assertEquals('Test', $loadedModel->getName());
}
}
Run it with:
vendor/bin/phpunit app/code/Vendor/Module/Test/Integration/Model/ExampleTest.php
Functional Testing with MFTF
Magento Functional Testing Framework (MFTF) simulates real user actions. First, install it:
composer require --dev magento/magento2-functional-testing-framework
Let’s test adding a product to the cart. Create a test at dev/tests/acceptance/tests/functional/Vendor/Module/AddToCartTest.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
<test name="AddSimpleProductToCartTest">
<annotations>
<title value="Add simple product to cart"/>
<description value="Test adding a simple product to the shopping cart"/>
</annotations>
<before>
<createData entity="_defaultCategory" stepKey="createCategory"/>
<createData entity="_defaultProduct" stepKey="createProduct">
<requiredEntity createDataKey="createCategory"/>
</createData>
</before>
<amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="goToProductPage"/>
<click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/>
<waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/>
<see userInput="You added $$createProduct.name$$ to your shopping cart." selector="{{StorefrontMessagesSection.success}}" stepKey="seeSuccessMessage"/>
</test>
</tests>
Run the test with:
vendor/bin/mftf run:test AddSimpleProductToCartTest
Continuous Integration (CI) for Automated Testing
Running tests manually is good, but integrating them into your CI pipeline is better. Here’s a basic .github/workflows/tests.yml
for GitHub Actions:
name: Magento 2 Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
extensions: dom, curl, libxml, mbstring, openssl, pdo_mysql, tokenizer, xml, zip
coverage: none
- name: Install dependencies
run: |
composer install --no-interaction --no-progress
- name: Run unit tests
run: vendor/bin/phpunit app/code/Vendor/Module/Test/Unit/
- name: Run integration tests
run: vendor/bin/phpunit app/code/Vendor/Module/Test/Integration/
Common Testing Pitfalls to Avoid
- Not mocking dependencies – Unit tests should test one class at a time.
- Over-reliance on integration tests – They’re slower than unit tests.
- Ignoring test coverage – Aim for at least 70% coverage.
- Forgetting edge cases – Test invalid inputs, empty states, etc.
Final Thoughts
Automated testing might seem like extra work upfront, but it pays off quickly. Bugs caught early are cheaper to fix, and you’ll deploy with confidence. Start small—add a few unit tests, then expand to integration and functional tests.
Need help with your Magento 2 modules? Check out our extensions or optimized hosting solutions to keep your store running smoothly!