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!