Tuesday 8 March 2016

Unit testing: mocking out final, type-checked dependency

G'day:
This is a follow-on from my earlier article: "PHPUnit: trap for dumb players regarding mocking". In that article I described how I fell foul of trying to mock a method that had been declared final, and - basically - wasting time from a mix of me not thinking to RTFM, validating my assumptions, and PHPUnit not being as declarative with its feedback as it could be.

Recap of previous article

To recap/augment the situation, I had a Logger class like this:

namespace me\adamcameron\mocking\service;

use me\adamcameron\mocking\exception\NotImplementedException;

class LoggingService {

    public final function logSomething($text){
        throw new NotImplementedException(__FUNCTION__ . " not implemented yet");
    }

}

And we have a service using that Logger as a dependency:

namespace me\adamcameron\mocking\service;

class MyService {

    private $logger;

    public function __construct(LoggingService $logger){
        $this->logger = $logger;
    }

    public function doesStuff($value){
        $preppedValue = "PREPPED $value";
        $processedValue = $this->doesRiskyStuff($preppedValue);
        $this->logger->logSomething($processedValue);
        return "RESULT OF DOING STUFF ON $processedValue";
    }

    protected function doesRiskyStuff($preppedValue){
        return "$preppedValue (SURVIVED RISKY PROCESS)";
    }

}

We've established we cannot mock that dependency because I cannot mock that final method.

Final prevents mocking

Our tactic to work around this was to create a different stubbed LoggingService which has the same methods, but not the final restriction:

namespace me\adamcameron\mocking\stub;

use me\adamcameron\mocking\exception\StubMethodCalledException;

class LoggingService {

    public function logSomething($text){
        throw new StubMethodCalledException(__FUNCTION__ . " must be mocked");
    }

}

And then we use StubMethodCalledException for our mocked dependency:

class MyServiceTest extends \PHPUnit_Framework_TestCase {

    // ...

    function setup(){
        $mockedLogger = $this->getMockedLogger();
        $this->myService = $this->getTestMyService($mockedLogger);
    }

    // ...

    function getMockedLogger(){
        $mockedLogger = $this->getMockBuilder('\me\adamcameron\mocking\stub\StubbedLoggingService')
            ->setMethods(["logSomething"])
            ->getMock();

        $mockedLogger->expects($this->once())
            ->method("logSomething")
            ->with("MOCKED RESPONSE FROM DOESRISKYSTUFF");

        return $mockedLogger;
    }

    // ...

}

Type-checking can be unhelpful

And now we run our tests and...


C:\src\php\php.local\www\experiment\phpunit\mock>phpunit
PHPUnit 4.8.23 by Sebastian Bergmann and contributors.

.E

Time: 764 ms, Memory: 5.00Mb

There was 1 error:

1) me\adamcameron\mocking\test\service\MyServiceTest::testDoesStuff
Argument 1 passed to me\adamcameron\mocking\service\MyService::__construct() must be an instance of me\adamcameron\mocking\service\LoggingService, instance of Mock_StubbedLoggingService_e4bcfaaf given

C:\src\php\php.local\www\experiment\phpunit\mock\src\service\MyService.php:9
C:\src\php\php.local\www\experiment\phpunit\mock\test\service\MyServiceTest.php:44
C:\src\php\php.local\www\experiment\phpunit\mock\test\service\MyServiceTest.php:16
C:\Users\adam.cameron\AppData\Roaming\Composer\vendor\phpunit\phpunit\src\TextUI\Command.php:149
C:\Users\adam.cameron\AppData\Roaming\Composer\vendor\phpunit\phpunit\src\TextUI\Command.php:100

FAILURES!
Tests: 2, Assertions: 0, Errors: 1.

C:\src\php\php.local\www\experiment\phpunit\mock>


Ah FFGS.

Indeed, here's that constructor:

public function __construct(LoggingService $logger){
    $this->logger = $logger;
}

Note how it type-checks the $logger argument.

For web application code, I would simply never bother type checking arguments. PHP is a dynamic, run-time compiled language, so type-checking really makes little sense in most situations. In this situation it's one of our API libraries, so it kinda makes sense. Normally I'd go "an API should typecheck as much as possible", but to me that only applies to APIs which will be used as a third-party module. For internal libraries - even shared ones - it's just boilerplate for the sake of it, IMO.


This is important, so I'm gonna make it bold:

That aside, in a situation you know the library is going to be used as a dependency of other code - especially code you know is going to be using TDD and dependency injection - do not type check on an implementation. Use an interface.

If we'd used an interface here, I could still use my stub, just by saying it implements the interface (which it does already!).


Stubbing and mocking and reflection

This leaves we with even more horsing around to do, but it's still doable.

Firstly, as I write this, it occurs to me I have skipped another hurdle here. You might be thinking "well MyService is your own code, so just get rid of the type-checking and be done with it". Well: yes and no. MyService is my code. However in reality MyService extends TheirService, and I am not in control over TheirService. One might think I could override TheirService's constructor I suppose - to have a more liberal approach to what it accepts as a logger - but this is a nono in PHP. Here's a quick example:

class Base {

    public function f(Dependency $d){

    }

}
class Sub extends Base {

    public function f($d){

    }

}

// testSub.php
$sub = new \me\adamcameron\typechecking\inheritance\Sub();

Notice how Base specifies a type of Dependency on the argument to f(), but Sub does not. And if I do this:

C:\src\php\php.local\www\experiment\typechecking\inheritance>php testSub.php

C:\src\php\php.local\www\experiment\typechecking\inheritance>php testSub.php
PHP Strict Standards:  Declaration of me\adamcameron\typechecking\inheritance\Sub::f() should be compatible with me\adamcameron\typechecking\inheritance\Base::f(me\adamcameron\typechecking\inheritance\Dependency $d) in C:\src\php\php.local\www\experiment\typechecking\inheritance\Sub.php on line 11

Strict Standards: Declaration of me\adamcameron\typechecking\inheritance\Sub::f() should be compatible with me\adamcameron\typechecking\inheritance\Base::f(me\adamcameron\typechecking\inheritance\Dependency $d) in C:\src\php\php.local\www\experiment\typechecking\inheritance\Sub.php on line 11

C:\src\php\php.local\www\experiment\typechecking\inheritance>


So that's no good.

TBH, I'd rather get the issue dealt with "at source" - I'll raise a ticket in the system to get TheirService sorted out properly. So I am stuck with the type-check in its constructor for the time being, anyhow.

We need to construct that MyService object with a real Logger, thanks to the implementation-based type-checking on MyService's constructor. However we don't want our MyService object to actually use a real Logger when testing. So I cheat.

There's a lot of code below, and each step has been extracted into separate methods, so you might need to scroll up and down to follow the flow.

Firstly I have abstracted a lot of work away. So here's my setup() method:

class MyServiceTest extends \PHPUnit_Framework_TestCase {

    private $myService;

    function setup(){
        $this->myService = $this->getTestMyService();
    }

    // [...]

}

In $this->getTestMyService() I do two things:

private function $this->getTestMyService(){
    $partiallyMockedMyService = $this->getPartiallyMockedMyService();
    $partiallyMockedMyService = $this->stubOutLogger($partiallyMockedMyService);

    return $partiallyMockedMyService;
}

First I get a mock of MyService:

private function $this->getPartiallyMockedMyService()(){
    $mockedLogger = $this->getMockedLogger();

    $partiallyMockedMyService = $this->getMockBuilder('\me\adamcameron\mocking\service\MyService')
        ->setConstructorArgs([$mockedLogger])
        ->setMethods(["doesRiskyStuff"])
        ->getMock();
    $partiallyMockedMyService->expects($this->once())
        ->method("doesRiskyStuff")
        ->with("PREPPED TEST VALUE")
        ->willReturn("MOCKED RESPONSE FROM DOESRISKYSTUFF");
    return $partiallyMockedMyService;
}

This will be familiar from the earlier article. I'm mocking out the helper method we don't want to execute, but set the expectations on how it should be called.

Also note I am constructing it with a mocked Logger:

private function $this->getMockedLogger(){
    $mockedLogger = $this->getMockBuilder('\me\adamcameron\mocking\service\LoggingService')
        ->disableOriginalConstructor()
        ->getMock();
    return $mockedLogger;
}

Again... this is the same as in the earlier article. So far, so good. We have not progressed at all. The next bit is the key bit. We replace the mocked logger with a stub of our own. Recall the second statement in getTestMyService():

private function getTestMyService(){
    $partiallyMockedMyService = $this->getPartiallyMockedMyService();
    $partiallyMockedMyService = $this->stubOutLogger($partiallyMockedMyService);

    return $partiallyMockedMyService;
}

Here's: stubOutLogger():

private function stubOutLogger($myService){
    $stubbedLogger = $this->getStubbedLogger();
    ReflectionHelper::setPrivateProperty(
        '\me\adamcameron\mocking\service\MyService',
        $myService,
        'logger',
        $stubbedLogger
    );
    return $myService;
}

This is where the key element comes into play. The setPrivateProperty() method in ReflectionHelper:

public static function setPrivateProperty($class, $object, $property, $value) {
    $reflectionClass = new \ReflectionClass($class);
    $reflectionProperty = $reflectionClass->getProperty($property);

    $reflectionProperty->setAccessible(true);
    $reflectionProperty->setValue($object, $value);
}


This uses PHP's ReflectionClass to change the accessibility of the passed-in property to be accessible... and then changes its value to the designated one. So be basically batter our way into the object and get rid of the mocked Logger, replacing it with our stubbed one. So now when we call its logSomething from the method we're testing... we are calling our mock.

One caveat to note here is that whilst the ReflectionClass constructor claims to take either a class (or a string reference to its name) or an object, I could only get this to work if I passed the classname, not the object. When I tried to use an object, the call to getProperty() errored saying the property did not exist. Look out for that.

Back to theexample code: leveraging that Reflection functionality, getStubbedLogger() mocks a stubbed logger instance (see further down), and sets expectations on the method of it that will be called.

private function getStubbedLogger(){
    $mockedLogger = $this->getMockBuilder('\me\adamcameron\mocking\stub\StubbedLoggingService')
        ->setMethods(["logSomething"])
        ->getMock();

    $mockedLogger->expects($this->once())
        ->method("logSomething")
        ->with("MOCKED RESPONSE FROM DOESRISKYSTUFF");

    return $mockedLogger;
}

Here's the stubbed logger:

namespace me\adamcameron\mocking\stub;

use me\adamcameron\mocking\exception\StubMethodCalledException;

class LoggingService implements Logger {

    public function logSomething($text){
        throw new StubMethodCalledException(__FUNCTION__ . " must be mocked");
    }

}


Note how its logSomething() is not final. So mocking it will work. And we're all happy. Well: if it didn't take all this hoop-jumping I'd be happier, but - shrug - so be it.

Overall Recap

What a mission. But the take-away from this article is that one can work around testing hurdles that final methods and implementation-based type-checking throw at us by using reflection to access - and change - private properties. Simply initialise yer dependency with a "real" implementation, and then replace it with your mocked implementation.

Of course the better approach would be to not use final, and to not type-check via implementation, but sometimes that's unavoidable.

Righto.

--
Adam