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:

Feb 25, 2025 - 11:46
 0
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:



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;
    }
}

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.



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');
    }
}

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!