Php – How to pass a mock object into a class for unit tests

mockingPHPunit testing

There seem to be many ways to pass a mocked object into a class for unit testing and I am unsure which is the proper approach to take for my PHP application.

If I was using Dependency Injection then I could have the dependencies as constructor arguments which I could use to pass mocks in from my tests:

class Example
{
    private $dependency;

    public function __construct(Dependency $dependency)
    {
        $this->dependency = $dependency;
    }
}

However, I'm not using Dependency Injection so I cannot use this approach.

Instead, I could add a public method to set the private property which is called from within the constructor:

class Example
{
    private $dependency;

    public function __construct()
    {
        $this->setDependency(new Dependency());
    }

    public function setDependency(Dependency $dependency)
    {
        $this->dependency = $dependency;
    }
}

My tests can now use this public method to set the dependency to my mocked value.
This seems like the best approach, however I've added this new setDependency method just to make testing easier and that feels a bit dirty.

Alternatively, I could use reflection to set the value of a protected property directly, but now my test needs knowledge of the implementation of the Example class:

class Example
{
    protected $dependency;
}

class ExampleTest
{
    public function testDependency()
    {
        $example = new Example();

        $dependencyMock = $this->getMock(); // Truncated

        $this->setPrivateProperty($example, "dependency", $dependencyMock);
    }
}

What is the right way for my tests to pass a mocked object into my Example class when I'm not using Dependency Injection?

Best Answer

However, I'm not using Dependency Injection so I cannot use this approach.

Are you sure you're not? Because when you write code like this:

class Example
{
    private $dependency;

    public function __construct(Dependency $dependency)
    {
        $this->dependency = $dependency;
    }
}

That screams dependency injection at me (your first Example above is ready for constructor injection). After all dependency injection is nothing more then a fancy term for passing a parameter in to become part of state. Some like to use DI container frameworks or libraries to do their DI in what amounts to a new language but others like to stick with the tools built into their language and use Pure DI. What you provided above is an example of Pure DI.

Either way you do it the main point is to separate construction code from behavior code. So long as you do that you get DI's main benefit: the ability to reconfigure without much rewriting.

A simple pattern for using Pure DI is to do the composing (constructing) in main (or as far up the call stack as you can get). But in PHP main is just the first line of code. So assume that's where the following lives:

$example = new Example( new Dependency() );

There's your injection. No DI container needed. If you have more injections for more objects to do you put them here as well. Doing this you can build up a connected object graph. If you need to change Dependency so that it depends on something else you build that in here as well. Whether you give them $ names is up to you. Whatever is most readable. Just don't go nuts1. Remember, names are a good thing.

Once everything is composed/constructed you write one line, and one line only, of behavior code to start your object graph ticking. Something like:

$example.start();

This is the end of your procedural code. Everything else is object oriented.

You also offered:

class Example
{
    private $dependency;

    public function __construct()
    {
        $this->setDependency(new Dependency());
    }

    public function setDependency(Dependency $dependency)
    {
        $this->dependency = $dependency;
    }
}

My tests can now use this public method to set the dependency to my mocked value. This seems like the best approach, however I've added this new setDependency method just to make testing easier and that feels a bit dirty.

What makes this dirty is that it forces the class to be mutable (state can change after construction) only for the sake of testing when testing doesn't need it. Testing was perfectly happy with the first example.

But you have something here that you didn't have before. What you have here is a known-good-default-value. It's a royal pain to have to configure everything when there are obvious defaults. Convention over configuration argues that it is better to design a system with a convention of known good defaults that can be overridden by adding configuration code, not simply changing configuration code.

There is a way to give testing, convention, and configuration everything they need, all without forcing the object to be mutable:

class Example
{
    private $dependency;

    public function __construct( Dependency $dependency = new Dependency() )
    {
        $this->dependency = $dependency;
    }
}

This makes use of optional arguments2. Now when you want the convention you can use:

$example = new Example();

and it's the same as if you used

$example = new Example( new Dependency() );

yet you are free to write

$example = new Example( new DependencyMock() );

in your tests or use it in your code to configure away from the convention.

Yes, technically the convention is hard coded. But the hard coded value is an overridable default value. That makes a world of difference. If you need to use factory methods this same trick works with them as well.

Lastly you hit us with this:

class Example
{
    protected $dependency;
}

And then you start talking about using reflection in your tests.

What is the right way for my tests to pass a mocked object into my Example class when I'm not using Dependency Injection?

The right way is to rewrite to accept injections. As shown above that doesn't require any fancy tools. You can use reflection to ignore encapsulation protections but it's generally better to parameterize and let the injections come in the front door rather then make them break your windows in the dark of night.