Writing a Unit Test for Magento 2

How to write a unit test in Magento 2, for those with prior knowledge of PHPUnit Software.

This article is for those with prior knowledge of PHPUnit software and will cover 'How to Write a Unit Test in Magento 2' using a real-world example. The basics, definitions and benefits of PHPUnit are scoped out, these are well-documented topics and we will focus mainly on writing a practical PHPUnit test with Magento 2. 

Unit testing can help ensure your development and code are of the highest quality, helping avoid problems in advance and therefore saving your project time and money.

Real life unit test:

For a given customer module i.e app/code/Vendor/ModuleToTest, our unit tests will reside inside the Test/Unit folder based on naming standards. Often they end up with a folder structure that's similar to a Magento module, with all the testable components in their corresponding folders:

Creating a Unit Test File

We will be writing a unit test for the Scan controller within our custom module:

Below is the content of the class we will be testing; All it does really is get the order id from the request param and passes it to the order repository, which in turns returns a matching order if it exists. 


namespace Develodesign\BarcodeScanner\Controller\Adminhtml\Barcodescanner;

use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Api\OrderRepositoryInterface;

class Scan implements HttpGetActionInterface
     * @var RequestInterface
    protected $request;

     * @var OrderRepositoryInterface
    protected $orderRepository;

     * @var Json
    protected $result;

     * Constructor
     * @param RequestInterface         $request
     * @param OrderRepositoryInterface $orderRepository
     * @param JsonFactory              $resultJsonFactory
    public function __construct(
        RequestInterface         $request,
        OrderRepositoryInterface $orderRepository,
        JsonFactory              $resultJsonFactory
    ) {
        $this->request = $request;
        $this->orderRepository = $orderRepository;
        $this->result = $resultJsonFactory->create();

     * @return ResponseInterface|Json|ResultInterface
     * @throws LocalizedException
    public function execute()
        $orderId = (int)$this->request->getParam('order_id');
        if (!$orderId) {
            throw new LocalizedException(
                new \Magento\Framework\Phrase('Query parameter, Order ID is required to perform this action')

        $order = $this->orderRepository->get($orderId);

        if ($order) {
                'success'  => true,
                'error'    => false,
                'response' => $order->getData()

        return $this->result;

We will be creating a corresponding test class ScanTest,  therefore the whole path will be:

Adding Modifiers, Matching Constructor Signature and Instantiating Class in our Test Class

We will start off by overriding the set-up method, this being the method called before each test. Within this method, we will match the modifiers, and constructor signature, and instantiate the class which we are testing. We will end up with this:


Matching the modifiers in the main class i.e Scan.php  

namespace Develodesign\BarcodeScanner\Test\Unit\Controller\Adminhtml;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\App\RequestInterface; use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Develodesign\Barcodecanner\Controller\Adminhtml\Barcodescanner\Scan;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\Json;

class Scan implements TestCase
/** * @var Scan */
private $controller; /** * @var RequestInterface|MockObject; */ private $requestInterfaceMock; /** * @var OrderRepositoryInterface|MockObject */ private $orderRepositoryMock; /** * @var Json|MockObject */ private $resultJsonMock; /** * @var JsonFactory|MockObject */ private $resultJsonFactoryMock;

Matching Constructor Signature and class instantiation

Instantiating our main class and matching its constructor signature. 

protected function setUp(): void
    $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)

    $this->requestInterfaceMock = $this->getMockBuilder(RequestInterface::class)

    $this->resultJsonFactoryMock = $this->createMock(JsonFactory::class);

    $this->resultJsonMock = $this->createMock(Json::class);


    $this->controller = new Scan(

Writing a Unit Test:

We can now proceed to write test cases for the execute method within our Scan class. The Order Id (order_id) is a required parameter from the request and as such an exception is thrown when it's not set. We will write a test case "testExecuteNullRequestParam()” which will cover this base as shown below:

 * @return void
 * @throws LocalizedException
public function testExecuteNullRequestParam(): void
    $orderId = null;
    $param = 'order_id';
    $this->expectExceptionMessage('Query parameter, Order ID is required to perform this action');

PHPUnit enables us to use its various testing functionalities such as the expects method which accepts an invocation order i.e method invoked once or any. We can specify arguments using with($param) and return value will Return($orderId). Furthermore, as our class is extending the PHPUnit, it allows us to make method calls such as expectException and expectExceptionMessage. We can define multiple test methods in the same test model. Here is the full file with other test cases

Running a Unit Test

We can run this single unit test via the CLI php: 

vendor/phpunit/phpunit/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/Develodesign/BarcodeScanner/Test/Unit/Controller/Adminhtml/ScanTest.php 

Incorporating Unit testing into your development process can help fix bugs throughout the development, preventing the delay of launches.

Do you need support from our Magento Certified Professionals?



Ready to start your next project?

See how we can help you accelerate your business