DEV Community

Nikola Stojiljkovic
Nikola Stojiljkovic

Posted on • Edited on

The most efficient way to debug problems with PHPUnit mocks

PHPUnit can be overwhelming to those who are just learning the basics of programming and/or unit testing in PHP. It is a powerful and robust tool which has been a cornerstone of unit testing in PHP world for many years now, which means it has a huge set of features covering almost any case you could encounter.

One of the most powerful features of PHPUnit are stubs and mocks. They give you the ability to detach the rest of your code from the part which you are trying to test. You can mock anything - from (other) methods in a class you are testing to dependencies, abstract classes or even traits and interfaces.

The first step, of course, is building the mock, and it usually goes like this:

$serviceMock = $this->getMockBuilder(YourService::class)
    ->disableOriginalConstructor()
    ->onlyMethods(['anotherMethodInYourClass', 'yetAnotherMethod'])
    ->getMock();
Enter fullscreen mode Exit fullscreen mode

Apart from the methods pictured in the above example, you also have many others, like setConstructorArgs() and setMethods(), but we will not go deeper into what they do. What is important in our case is that there are many ways of how exactly you are going to create a mock object, which depends on your needs. You may need to mock a set of dependencies, an abstract class, an interface,...

If you are still learning (or even if you don't keep up with the changes between versions of PHPUnit), you may end up "guessing" which set of methods you are going to call when creating your mocks. Even StackOverflow is full of answers like "The following way of creating a mock works for me" - without any information or context provided.

Well, let's provide some context.

PHPUnit, under the hood, is creating a mock class just like you would write any class. It generates source code for the class, trait or interface which you are trying to mock and fills it with mocked property and return values. One of the checks PHPUnit performs is that the generated mock class code is a valid PHP code. This is done with a less known built-in PHP function eval($someString). This function evaluates whether $someString contains a valid PHP code. A bit of warning, though, as the official documentation states: The eval() language construct is very dangerous because it allows execution of arbitrary PHP code. Its use thus is discouraged. If you have carefully verified that there is no other option than to use this construct, pay special attention not to pass any user provided data into it without properly validating it beforehand.

That being said, PHPUnit prepares the source code of the generated mocked class and stores it in a string, which is then evaluated using the eval() function.

If you are getting errors while trying to create or use mocks, you are not sure what exactly is happening under the hood and how the generated mock class actually looks like - there is a way to see the actual contents (complete source code) of the mocked class, during debugging.

The key point to look at is the PHPUnit\Framework\MockObject\MockClass class and its generate() method. In PHPUnit 9.6 it goes like this:

    public function generate(): string
    {
        if (!class_exists($this->mockName, false)) {
            eval($this->classCode);

            call_user_func(
                [
                    $this->mockName,
                    '__phpunit_initConfigurableMethods',
                ],
                ...$this->configurableMethods
            );
        }

        return $this->mockName;
    }
Enter fullscreen mode Exit fullscreen mode

If you set a breakpoint at eval($this->classCode) you will be able to see the contents of $this->classCode which, obviously, contains the complete source code of the generated mock class. The best part is - it's even formatted properly, so you won't have any issues reading it.

You are now free to experiment with different types of mocks and options for their creation, while looking at how exactly your way of creating mocks affects the end result - the mocked object. Have fun!

If you liked the article,...
Image description

Top comments (0)