PHPUnit reflection test example
PHPUnit reflection test example

I’ve been rejected many times in the recruitment processes. I was also unsuccessful over and over proving my practical or theoretical skills for freelancing contracts. Typically, the reason was obvious and reasonable. Once, there was an interview I’ll probably for a long time. I failed because I couldn’t simply explain why my unit tests look like that.

Why was so hard to explain?

Reflections in my tests were present since always. I’m used to them that much, I don’t question their purpose anymore. They don’t surprise me in somebody’s code as well.

This should make it trivial to explain. Like “why are we breathing”, right? But I failed to do so.

My soft skills weren’t certainly helpful at that time too. I’m not either entrepreneur or marketer. I do code, not the “talking”. So let me gently explain this one and last time.

Who uses reflections?

I was unlucky to start my career in a small city, in small teams which didn’t honour code coverage. I bet you know that kind of companies. Amount over quality.

I had nobody to learn from how to properly test my code. Most of what I know I learnt by myself with huge assistance of uncle google, couple books and practice.

So here I’m confidently admitting I use them. They work for me just perfectly. They saved my ass many times. Here’s why.

Let’s get dirty

If you’ve been testing your code for a while, or read Uncle Bob, you might not like it. And you’re totally right! I’m totally for testing only public methods (interfaces). Consider this article playing with PHP and trying new things – perhaps someday you will encounter the requirement of doing it in your project.

First, let’s agree that nobody is perfect. Programmers do make mistakes. We are lazy. Laziness is usually desirable programmer feature. But, it’s definitely not good regarding testing your code.

I like to have full control over my code. When I write a unit test case, I’m aiming for having every atomic assertion in a separate test. I’m not fanatic about that, this is not possible everywhere, but such structure, when tests fail, gives me an accurate reference to where my code works incorrectly.

Some may say this is redundant and unnecessary. I agree on first, but I can’t say they are not necessary. To prove that, let’s have a look at this class below. It’s slightly modified boilerplate class from PHP: The right way (if you never heard of it, what are you still doing here?).

<?php

namespace Src;

class Vehicle
{
    private $make;
    private $model;

    public function __construct(string $make, string $model)
    {
        $this->make = $make;
        $this->model = $model;
    }

    public function getMakeAndModel(): string
    {
        return $this->getMake() . ' ' . $this->getModel();
    }

    public function getMake(): string
    {
        return $this->make;
    }

    public function getModel(): string
    {
        return $this->model;
    }
}

Pretty straight-forward, right? There are few getters and setters, and some basic initialisation in the constructor. Testing it is really easy and could look like this.

<?php

namespace Tests;

use PHPUnit\Framework\TestCase;
use Src\Vehicle;

class VehicleTest extends TestCase
{

    public function testGetMakeAndModel()
    {
        $vehicle = $this->createPartialMock(Vehicle::class, [
            'getMake',
            'getModel',
        ]);

        $vehicle
            ->expects($this->once())
            ->method('getMake')
            ->willReturn('foo');

        $vehicle
            ->expects($this->once())
            ->method('getModel')
            ->willReturn('bar');

        /** @var Vehicle $vehicle */

        $this->assertSame('foo bar', $vehicle->getMakeAndModel());
    }

    public function testGetMake()
    {
        $this->assertSame('foo', (new Vehicle('foo', 'bar'))->getMake());
    }

    public function testGetModel()
    {
        $this->assertSame('bar', (new Vehicle('foo', 'bar'))->getModel());
    }

}

The test passes as expected. Nothing, so far, can go wrong. We tested every feature separately and wee would know exactly what’s failing.

PHPUnit 7.5.1 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 31 ms, Memory: 4.00MB

OK (3 tests, 4 assertions)

When something goes wrong inside getter, you will get harmful red E or F, instead of a peaceful dot.

Time passes, our class gets more complicated. Initialisation goes crazy, it’s not that predictable. Certainly, something might crash down there.

<?php

namespace Src;

class Vehicle
{

    // ...

    public function __construct(string $make, string $model)
    {
        // ...
        $this->price = mt_rand(1000, 3000);
    }

    public function getPrice(): int
    {
        return $this->price;
    }

    // ...

}

Let’s test constructor price generation and price getter.

public function testConstructorPrice()
{
    $vehicle = new Vehicle('foo', 'bar');

    $this->assertGreaterThanOrEqual(1000, $vehicle->getPrice());
    $this->assertLessThanOrEqual(3000, $vehicle->getPrice());
}

public function testGetPrice()
{
    $this->assertSame('???', (new Vehicle('foo', 'bar'))->getPrice());
}

First two assertions aren’t really precise, don’t they? Forget about it and look at price getter test. This is a more interesting example. How possibly could that be testes without passing it by constructor?

Remember about keeping every atomic operation separate? This function is dependent by property and can’t be mocked easily by PHPUnit built-in MockBuilder. Here come reflections.

public function testGetPrice()
{
    $vehicle = $this->createPartialMock(Vehicle::class, []);

    $reflection = new \ReflectionProperty(Vehicle::class, 'price');
    $reflection->setAccessible(true);
    $reflection->setValue($vehicle, 1);

    /** @var Vehicle $vehicle */

    $this->assertSame(1, $vehicle->getPrice());
}

Now, this test checks only that, what it is supposed to test. This means – check if getPrice returns price property. Nothing else than that. Single operation.

Where could that be possibly useful?

Well, the easiest example could be when constructor fails or acts unpredictably (anyway, they also should be wisely tested, and shouldn’t perform complex operations). Let’s look change make property assignment into this.

if (in_array($make, self::$makes)) {
    $this->make = $make;
} else {
    $this->make = self::$makes[rand(0, count(self::$makes) - 1)];
}

If we keep old testGetMake, we could possibly get this result. There is a failure on getName method. How could that happen? It’s so simple, nothing can possibly crash.

PHPUnit 7.5.1 by Sebastian Bergmann and contributors.

..F..                                                               5 / 5 (100%)

Time: 85 ms, Memory: 4.00MB

There was 1 failure:

1) Tests\VehicleTest::testGetMake
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'foo'
+'Chevrolet'

/application/tests/VehicleTest.php:43

FAILURES!
Tests: 5, Assertions: 9, Failures: 1.

And you will be right assuming that. This getter works properly. Problem is with the constructor.

This result could be hard to analyse if you had a larger test suite, here is not a big deal, but I hope this shows the potential difficulty in finding where that failure comes from.

Conclusion

Don’t use reflections if you don’t like them. Don’t use them if you think they are redundant, and you can’t live with that in your mind. It’s really individual.

This approach works for me and some other developers I’ve met. If you don’t know how to decouple your tests responsibilities, I recommend you try this way for a while. I’m pretty sure you won’t regret it.

Let me know, what do you think about this. Do you think it’s a waste of time or cleaner way to test your code?

Leave a comment

Your email address will not be published. Required fields are marked *