HAPPYACCIDENTS

Dependency injection as a tool for testing

      COMMENTS
  PHP, Dependency Injection, Unit Testing, PHPUnit

Dependency injection is a simple design pattern that provides several benefits, one of which is the ability to vastly improve your unit test suite. In this post I am going to be looking at how this is done and how it relates to certain testing methodologies.

Please be aware that all code samples are massively simplified for the purpose of this post and do not necessarily follow best coding practices.

Unit Testing

"Unit testing is a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine if they are fit for use. Intuitively, one can view a unit as the smallest testable part of an application." - Wikipedia

In PHP I like to view a unit as a method of a class. This allows me to run one or multiple unit tests on each method that I write, and to write the code to have a single responsibility and to follow the UNIX philosophy of "Do one thing, and do it well.".

There are other types of testing that should be employed alongside your unit tests, but for the purpose of this post I will only be looking at unit tests.

A Unit

Let's take a look at some code.

<?php

class Foo
{
    public function doSomething($id, $name, $email)
    {
        $bar = new Bar;

        // .. possibly manipulate $id, $name and $email

        // assume we want to edit a row in a database and
        // then return an array of data containing that row
        return $bar->editAndReturnUser($id);
    }
}

It is impossible to unit test this code without allowing the doSomething() method to instantiate the real object Bar. As soon as we instantiate the concrete implementation of a class, the test is no longer only testing this unit of code, and therefore we have increased the amount of places that the test can fail.

If we follow the definition of unit testing, this unit is not the "smallest testable part" of our application.

We can refactor the method to receive its dependency Bar as an argument and therefore inject it at runtime instead of instantiating it.

<?php

class Foo
{
    protected $bar;

    public function doSomething($bar, $id, $name, $email)
    {
        $this->bar = $bar;

        // .. possibly manipulate $id, $name and $email

        // assume we want to edit a row in a database and
        // then return an array of data containing that row
        return $this->bar->editAndReturnUser($id, $name, $email);
    }
}

Dependency injection as a design pattern is that simple, now we can inject Bar as an argument of our method. In testing terms, this means that we can now verify state on Bar after the method is invoked.

<?php

class FooTest extends PHPUnit_Framework_Testcase
{
    public function testDoSomethingEditsDataAndReturnsArray()
    {
        $foo = new Foo;
        $bar = new Bar;

        $user = $foo->doSomething($bar, 1, 'Phil', 'hello@example.com');

        $this->assertInternalType('array', $user, 'The return of (doSomething) was not an array');

        // .. assert that Bar has changed the state of the database
    }
}

This is still quite complicated and requires us to access a database or at the very least an abstraction of the database.

Martin Fowler describes the unit we are testing as the System Under Test and any dependencies that we interact with as Collaborators. By abstracting any collaborators, we are able to simplify the process and minimise the places where our test can fail. I want to know that if a test fails, it fails in the System Under Test.

Test Doubles

A test double is an object that looks and behaves like its release-intended counterpart. Gerard Meszaros describes at least 5 types of test double. I am going to be looking at 3 in particular. Fakes, Stubs and Mocks.

Testing with a Fake

By refactoring the test, we can inject a Fake of Bar and control the implementation of its methods.

<?php

class FakeBar
{
    public $users = [
        1 => [
            'name'  => 'Adam',
            'email' => 'adam@example.com'
        ]
    ];

    public function editAndReturnUser($id, $name, $email)
    {
        $this->users[$id]['name']  = $name;
        $this->users[$id]['email'] = $email;

        return $this->users[$id];
    }
}

class TestFoo extends PHPUnit_Framework_Testcase
{
    public function testDoSomethingEditsDataAndReturnsArray()
    {
        $foo = new Foo;
        $fakeBar = new FakeBar;

        $user = $foo->doSomething($fakeBar, 1, 'Phil', 'hello@example.com');

        $this->assertInternalType('array', $user, 'The return of (doSomething) was not an array');
        $this->assertSame($user['name'], 'Phil', 'The returned user name was not as expected');
        $this->assertSame($user['email'], 'hello@example.com', 'The returned user email was not as expected');

        // verify that the state of FakeBar has changed
        $this->assertSame($fakeBar->users['name'], 'Phil', 'The user name in FakeBar was not changed');
        $this->assertSame($fakeBar->users['email'], 'hello@example.com', 'The user email in FakeBar was not changed');
    }
}

By injecting a Fake we have isolated the testing to the system under test and we simply verify state on the collaborator to ensure it has changed as expected. Separate unit tests of the real Bar object will handle the testing of the real implementation.

Testing with a Stub

There are several methods and tools to aid the creation of stubs. One method would be to hard code the stub in a similar way to a fake, the difference here is that the stub provides no implementation but only returns a predefined value. For this example though, I will be using PHPUnit's mocking framework to create the stub for me. Stay with me here, although it is a mocking framework, it is still possible to create a stub, I will explain the differences in creation later in the post.

<?php

class FooTest extends PHPUnit_Framework_Testcase
{
    public function testDoSomethingEditsDataAndReturnsArray()
    {
        $foo = new Foo;

        $user = [
            'name'  => 'Phil',
            'email' => 'hello@example.com'
        ];

        $stubBar = $this->getMock('Bar');

        $stubBar->expects($this->any())
                ->method('editAndReturnUser')
                ->will($this->returnValue($user));

        $foo->doSomething($stubBar, 1, 'Phil', 'hello@example.com');

        $this->assertInternalType('array', $user, 'The return of (doSomething) was not an array');
        $this->assertSame($user['name'], 'Phil', 'The returned user name was not as expected');
        $this->assertSame($user['email'], 'hello@example.com', 'The returned user email was not as expected');
    }
}

By using a stub, we have provided a hard coded return value from our collaborator. We have also eliminated the need to verify state on our collaborator. This is a pure unit test. We provide the "indirect input" to be returned by the collaborator and only test the code in the system under test. Again, we are relying on the real implementation of Bar to have its own unit tests.

Testing with a Mock

As discussed earlier in this post, a mock is used for verification. We want the mock to ensure that the method is invoked the correct amount of times and that it has been passed the correct arguments or "indirect output".

<?php

class FooTest extends PHPUnit_Framework_Testcase
{
    public function testDoSomethingEditsDataAndReturnsArray()
    {
        $foo = new Foo;

        $user = [
            'name'  => 'Phil',
            'email' => 'hello@example.com'
        ];

        $mockBar = $this->getMock('Bar');

        $mockBar->expects($this->once())
                ->method('editAndReturnUser')
                ->with($this->equalTo(1), $this->equalTo('Phil'), $this->equalTo('hello@example.com'))
                ->will($this->returnValue($user));

        $foo->doSomething($mockBar, 1, 'Phil', 'hello@example.com');

        $this->assertInternalType('array', $user, 'The return of (doSomething) was not an array');
        $this->assertSame($user['name'], 'Phil', 'The returned user name was not as expected');
        $this->assertSame($user['email'], 'hello@example.com', 'The returned user email was not as expected');
    }
}

The difference here as opposed to the stub is that we are telling the object to verify that the method is invoked only once, and that the method is passed specific arguments. The mock will verify these conditions are met and throw an exception if they are not. This is essentially verifying state on the collaborator with "indirect output".

And I'm Off

Hopefully this post has given an insight into just one of the reasons that dependency injection is such a powerful design pattern. It should not however, be mistaken for, or considered to be more than it is. Dependency injection is part of a larger theory and set of methods. I would recommend reading The "D" Doesn't Stand For Dependency Injection by Brandon Savage for a little more clarification on this.

Phil Bennett

I'm a Yorkshireman living and working in London as a Senior Developer for VideoGamer.com. I also create and contribute to Open Source Software.

comments powered by Disqus