DEV Community

Cover image for Automated Tests | How to mock classes with PHPUnit 🔧
Raziel Rodrigues
Raziel Rodrigues

Posted on • Edited on

Automated Tests | How to mock classes with PHPUnit 🔧

Introduction

Mocks are basically clones of your classes. They override your class while maintaining the same type and return type as the original class. Ensuring the correct return type during the mock process is essential; otherwise, errors will occur. Let's say we want to mock a class used inside our calculator.

How to Mock in PHPUnit

Following the previous code, add a new class called PrintCalculationService:

<?php

declare(strict_types=1);

namespace App\Service;

class PrintCalculationService
{
    function array(int $result): array
    {
        # a lot of business rules...
        return [
            'result' => $result
        ];
    }

    function print(int $result): string
    {
        # a lot of business rules...
        return 'result: ' . $result;
    }
}
Enter fullscreen mode Exit fullscreen mode

setUp and tearDown

These functions control the global state and execute before and after each test.

  • setUp -> Executes actions before each test.
  • tearDown -> Executes actions after each test.

createMock and createStub

These functions create a mock of your class. The key difference is that mocks provide more functionality than stubs. In other test frameworks, they have distinct purposes, but in PHPUnit, they are nearly identical.

  • createStub -> Creates a stub based on the class.
  • createMock -> Creates a mock based on the class.
  • ->method('print') -> Configures the method to return a specified value for testing purposes.
  • ->willReturn('result: 2') -> Defines the return value to satisfy and pass the test.

There are many other configurations available. You can find more details in the PHPUnit Test Doubles Documentation.

<?php

declare(strict_types=1);

namespace App\Tests\Unit\Service;

use App\Service\CalculatorService;
use App\Service\PrintCalculationService;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class CalculatorServiceTest extends TestCase
{
    private MockObject|PrintCalculationService|null $printCalculationService = null;

    protected function setUp(): void
    {
        $this->printCalculationService = $this->createMock(PrintCalculationService::class);
    }

    protected function tearDown(): void
    {
        $this->printCalculationService = null;
    }

    static function sumProvider() {
        yield [10, 10, 20];
        yield [1, 1, 2];
        yield [2, 2, 4];
    }

    #[Test]
    #[DataProvider('sumProvider')]
    function testSum($x, $y, $expected) {
        $service = new CalculatorService($this->printCalculationService);
        $this->assertSame(
            $service->sum($x, $y),
            $expected
        );
    }

    #[Test]
    function testDivision() {
        $service = new CalculatorService($this->printCalculationService);
        $this->assertEquals(
            $service->division(10, 10),
            1
        );
    }

    #[Test]
    function testDivisionException() {
        $this->expectException(\Exception::class);
        $service = new CalculatorService($this->printCalculationService);
        $service->division(0, 10);
    }

    #[Test]
    function testSumPrint() {
        $service = new CalculatorService($this->printCalculationService);
        $result = $service->sum(1, 1);
        $this->assertSame($result, 2);

        $this->printCalculationService->method('array')->willReturn([
            'result mock: 2'
        ]);

        $print = $service->show($result, 'array');
        $this->assertSame($print, ['result mock: 2']);

        $this->printCalculationService->method('print')->willReturn('result mock: 2');

        $print = $service->show($result, 'print');
        $this->assertSame($print, 'result mock: 2');
    }
}
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

This has been an insightful journey! I hope you enjoyed it. I aimed to be as concise and to the point as possible, making it easier to quickly access the information. As I mentioned, writing tests requires a different mindset. We need to think in reverse. Mocks are an excellent way to approach this concept since they allow us to manipulate results and test every possible path in a function.

Now I encourage you to go for more informations and best practives for your tests, this is just a ready to go and then you can already starting getting your code more senior

Additional Topics

  • Code Coverage
  • How to configure the PHPUnit XML file

Recommended Reading

GitHub Repository: Leave a star!

What do you think? Was this useful? Leave a comment!

Top comments (1)

Collapse
 
razielrodrigues profile image
Raziel Rodrigues

If you have any questions leave a comment!