Posts tagged 'unit tests'

Don't use the class under test to create the expected result

published on January 08, 2020.

From time to time I come across a mistake in unit tests that makes them useless. The mistake is that we use the class and method that we are testing to create the expected result of the test:

BadTest.php
<?php

declare(strict_types=1);

class BadTest extends TestCase
{
    public function testThatPasswordIsHashed(): void
    {
        $hasher = new PasswordHasher();

        $actualResult = $hasher->hash('super secret');

        $expectedResult = $hasher->hash('super secret');

        $this->assertSame($expectedResult, $actualResult);
    }
}

If we were to break the behavior of the hash method, the test would pass. Returning an empty string is enough to break it but leave the tests green.

The SortOfFixedTest example shows an improvement over the BadTest. The problem here is that we repeat the underlying algorithm in the test. Sometimes we can’t avoid this, but those are rare cases.

SortOfFixedTest.php
<?php

declare(strict_types=1);

class SortOfFixedTest extends TestCase
{
    public function testThatPasswordIsHashed(): void
    {
        $hasher = new PasswordHasher();

        $actualResult = $hasher->hash('super secret');

        $expectedResult = password_hash('super secret', PASSWORD_DEFAULT);

        $this->assertSame($expectedResult, $actualResult);
    }
}

Repeating algorithms like this will slow down the test suite. We are lazy, we would copy/paste the algorithm from the actual method. If we made a bug in the original algorithm, we would have it in our test as well.

We have to set the expected result to the result that we are expecting:

FixedTest.php
<?php

declare(strict_types=1);

class FixedTest extends TestCase
{
    public function testThatPasswordIsHashed(): void
    {
        $hasher = new PasswordHasher();

        $actualResult = $hasher->hash('super secret');

        $expectedResult = '$2y$10$PfAO94tkT3whsYZRpzAmG.aNb9HOVUP9j92zn2Nfc8Qi8bxv5rx8O';

        $this->assertSame($expectedResult, $actualResult);
    }
}

If the hash method changes, the test will fail. We are not repeating the underlying algorithm in our test code, so there is no chance of repeating a bug from the tested method.

Happy hackin’!

Tags: unit tests, testing.
Categories: Programming, Development.

Mocking hard dependencies with Mockery

published on December 23, 2014.

One problem with unit testing legacy applications is that the code has new statements all over the place, instantiating new objects in a way that doesn’t really makes it easier to test the code.

Of course, the easy answer to this is “Just refactor your application!", but that’s almost always easier said than done.

If refactoring is an option, do it. If not, one option is to use Mockery to mock the hard dependencies.

One prerequisite to make this work is that the code we are trying to test uses autoloading.

Let’s take the following code for an example:

<?php
namespace App;
class Service
{
    function callExternalService($param)
    {
        $externalService = new Service\External();
        $externalService->sendSomething($param);
        return $externalService->getSomething();
    }
}

The way we can test this without doing any changes to the code itself is by creating instance mocks by using the overload prefix.

<?php
namespace AppTest;
use Mockery as m;
class ServiceTest extends \PHPUnit_Framework_TestCase {
    public function testCallingExternalService()
    {
        $param = 'Testing';

        $externalMock = m::mock('overload:App\Service\External');
        $externalMock->shouldReceive('sendSomething')
            ->once()
            ->with($param);
        $externalMock->shouldReceive('getSomething')
            ->once()
            ->andReturn('Tested!');

        $service = new \App\Service();

        $result = $service->callExternalService($param);

        $this->assertSame('Tested!', $result);
    }
}

If we run this test now, it should pass. Mockery does it’s job and our App\Service will use the mocked external service instead of the real one.

The problem whit this is when we want to, for example, test the App\Service\External itself, or if we use that class somewhere else in our tests.

When Mockery overloads a class, because of how PHP works with files, that overloaded class file must not be included otherwise Mockery will throw a “class already exists” exception. This is where autoloading kicks in and makes our job a lot easier.

To make this possible, we’ll tell PHPUnit to run the tests that have overloaded classes in separate processes and to not preserve global state. That way we’ll avoid having the overloaded class included more than once. Of course this has it’s downsides as these tests will run slower.

Our test example from above now becomes:

<?php
namespace AppTest;
use Mockery as m;
/**
 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled
 */
class ServiceTest extends \PHPUnit_Framework_TestCase {
    public function testCallingExternalService()
    {
        $param = 'Testing';

        $externalMock = m::mock('overload:App\Service\External');
        $externalMock->shouldReceive('sendSomething')
            ->once()
            ->with($param);
        $externalMock->shouldReceive('getSomething')
            ->once()
            ->andReturn('Tested!');

        $service = new \App\Service();

        $result = $service->callExternalService($param);

        $this->assertSame('Tested!', $result);
    }
}

And that should be pretty much it. If nothing else, it should make parts of old code easier to test.

For anyone interested, I put the example code up on Github.

Robert Basic

Robert Basic

Software developer making web applications better.

Let's work together!

I would like to help you make your web application better.

Robert Basic © 2008 — 2020
Get the feed