DRYer test dependency injection

Leveraging Prophecy instantiation and expectation definition in tests.

I won’t repeat myself, I won’t repeat myself, I won’t repeat myself

Tests are not code outside of code; as a software component moves on in its development tests will be left behind if their maintenance becomes a burden.
The first burden comes in the form of dependency injection.
While auto-magic dependency injection resolution (a la Laravel) might alleviate some suffering my usual context, WordPress, will not provide anything like that.
Furthermore automatic dependency resolution is a bliss in production but might prove too rigid during tests.

class ClassOneTest extends \PHPUnit_Framework_TestCase {

    public function setUp(){

    }

    public function tearDown(){

    }

    public function testOne()
    {
        $a = new A();
        $b = new B();
        $c = new C();
        $d = new D();
        $sut = new ClassOne( $a, $b, $c, $d);

        $sut->doSomething();

        $this->assertThat(...);
    }

    public function testTwo()
    {
        $a = new A();
        $b = new B();
        $c = new C();
        $d = new D();
        $sut = new ClassOne( $a, $b, $c, $d);

        $sut->doSomethingElse();

        $this->assertThat(...);
    }
}

This code feels repetitive and can be easily refactored into this

class ClassOneTest extends \PHPUnit_Framework_TestCase {

    public function setUp(){

    }

    public function tearDown(){

    }

    protected function makeInstance()
    {
        $a = new A();
        $b = new B();
        $c = new C();
        $d = new D();
        $sut = new ClassOne( $a, $b, $c, $d);

        return $sut;
    }

    public function testOne()
    {
        $sut = $this->makeInstance();

        $sut->doSomething();

        $this->assertThat(...);
    }

    public function testTwo()
    {
        $sut = $this->makeInstance();

        $sut->doSomethingElse();

        $this->assertThat(...);
    }
} 

Mocking then setting expectations

The refactoring above works if no expectation or mocking are needed on any one of the injected dependencies.
What happens if I need to set different expectations on different objects in different tests?

class ClassOneTest extends \PHPUnit_Framework_TestCase {

    public function setUp(){

    }

    public function tearDown(){

    }

    public function testOne()
    {
        $a = $this->prophesize('A');
        $a->someMethod()->willReturn(12);
        $b = new B();
        $c = new C();
        $d = new D();
        $sut = new ClassOne( $a, $b, $c, $d);

        $sut->doSomething();

        $this->assertThat(...);
    }

    public function testTwo()
    {
        $a = new A();
        $b = $this->prophesize('B');
        $b->anotherMethod()->willReturn(23);
        $c = new C();
        $d = new D();
        $sut = new ClassOne( $a, $b, $c, $d);

        $sut->doSomethingElse();

        $this->assertThat(...);
    }
}

This code cannot be refactored as the first one and the specific needs of each test method do not allow for the previous solution; there is but a fix that’s based on the possibility to mock something and later set expectations on such a mock.

class ClassOneTest extends \PHPUnit_Framework_TestCase {

    protected $a;
    protected $b;
    protected $c;
    protected $d;

    public function setUp(){
        $this->a = $this->prophesize('A');
        $this->b = $this->prophesize('B');
        $this->c = $this->prophesize('C');
        $this->d = $this->prophesize('D');
    }

    public function tearDown(){

    }

    protected function makeInstance()
    {
        return new ClassOne(
            $this->a->reveal(),
            $this->b->reveal(),
            $this->c->reveal(),
            $this->d->reveal()
        );
    }

    public function testOne()
    {
        $this->a->someMethod()->willReturn(12);
        $sut = $this->makeInstance();

        $sut->doSomething();

        $this->assertThat(...);
    }

    public function testTwo()
    {
        $this->b->anotherMethod()->willReturn(23);
        $sut = $this->makeInstance();

        $sut->doSomethingElse();

        $this->assertThat(...);
    }
}

The first thing to notice is that I’m injecting dummies for each dependency class by default (see prophecy dummies) and that will cover the case where dependencies are required to satisfy type-hinting and we have a good knowledge of which methods will be called on which dependencies and when.
If that’s not the case the makeInstance method can be modified; that’s the case for a dependency class that’s nothing but an information or value object.
In the refactoring above I know calling ClassOne::doSomething will trigger a call to A::someMethod while calling ClassOne::doSomethingElse will trigger a call to B::anotherMethod.
The refactoring does not remove the possibility to add a totally custom fixture set up

class ClassOneTest extends \Codeception\TestCase\Test {

    protected $a;
    protected $b;
    protected $c;
    protected $d;

    public function setUp() {
        $this->a = $this->prophesize( 'A' );
        $this->b = $this->prophesize( 'B' );
        $this->c = $this->prophesize( 'C' );
        $this->d = $this->prophesize( 'D' );
    }

    public function tearDown() {

    }

    protected function makeInstance() {
        return new ClassOne(
            $this->revealOrReturn( $this->a ),
            $this->revealOrReturn( $this->b ),
            $this->revealOrReturn( $this->c ),
            $this->revealOrReturn( $this->d )
        );
    }

    protected function revealOrReturn( $object ) {
        return $object instanceof Prophecy\Prophecy\ObjectProphecy ? $object->reveal() : $object;
    }

    public function testOne() {
        $this->a->someMethod()->willReturn( 12 );
        $sut = $this->makeInstance();

        $sut->doSomething();

        $this->assertTrue( true );
    }

    public function testTwo() {
        $this->b->anotherMethod()->willReturn( 23 );
        $sut = $this->makeInstance();

        $sut->doSomethingElse();

        $this->assertTrue( true );
    }

    public function testThree() {
        $this->a->someMethod()->willReturn( 'some value' );
        $this->b->anotherMethod()->willReturn( 'another value' );
        $this->c = new C();
        $this->d = new D();

        $sut = $this->makeInstance();

        $sut->doSomethingElse();

        $this->assertTrue( true );
    }
}

While the code might seem verbose and lengthy here moving the revealOrReturn function to a utility class will help.

Probably related

I appreciate your input