Injecting mocks into type-hinted parameters
January 23, 2014
I'm very fond of the use of interfaces over class inheritance in code and, while implementing the ViewController
class, in all its dependency injectability glory, I've got one reason more to do so.
What I'm testing
The ViewController
class sports a constructor with fully injectable dependencies that's meant to be used in factories (me, as a future user, would not like to be left in charge of remembering them all) and uses type hinting to suggest and funnel what it will actually take as a legit argument. The ViewController::__construct()
method signature reads like
public function __construct( \TAD\MVC\Helpers\FilePathResolver $FilePathResolver, \TAD\Helpers\Interfaces\GlobalFunctionsAdapter $globalFunctionsAdapter, \TAD\Interfaces\GlobalVariablesAdapter $globalVariablesAdapter, $elementName);
It proved quite hard to test; I made the constructor method accept all of its dependencies up-front first and foremost to be able to test it and not being able to do so proved quite a shock.
Not so fluent man!
Since I will mock all of ViewController
dependencies I begin mocking \TAD\MVC\Helpers\FilePathResolver
and being it a concrete class that's easily done in an helper method I've created in the test class
$className = 'TAD\MVC\Helpers\FilePathResolver';
$this->filePathResolver = $this->getMock( $className, array( "__construct" ), array(), $className, FALSE );
$this->filePathResolver->rootFilePath = '/folder';
aside for the last line, needed in the tests, I've (reading getMock
parameters):
- set the class to mock to
TAD\MVC\Helpers\FilePathResolver
- told the mock generator I want to mock the
__construct
method alone as it will make some checks - I'm passing no parameters to the class constructor
- I want the mocked class name to be the same as the original class (
TAD\MVC\Helpers\FilePathResolver
) sinceViewController::__construct
uses type hinting - I do not want the original constructor to be called
As a PHPUnit user I should be able to do the same using the fluent interface like
$this->filePathResolver = $this->getMockBuilder( $className )->setMockClassName( $className )->disableOriginalConstructor()->setMethods( array ( '__construct' ));
But that's not the case. While the first solution will pass the type hinting check the second fluent one will fail, no matter the order of the fluent methods call, with the message
ErrorException: Argument 1 passed to TAD\MVC\Controllers\ViewController::__construct() must be an instance of TAD\MVC\Helpers\FilePathResolver, instance of PHPUnit_Framework_MockObject_MockBuilder given
So the fluent interface seems not to work the same way as the classic (the getMock
) one and that's detailed in a GitHub issue about this very specific problem.
It gets easier with interfaces
Having successfully mocked the FilePathResolver
class the next lines in the test helper method will mock the two interface requirements
$className = 'TAD\Interfaces\GlobalFunctionsAdapter';
$this->functions = $this->getMock( $className );
$className = 'TAD\Interfaces\GlobalVariablesAdapter';
$this->globals = $this->getMock( $className );
And this works perfectly. The class naming problem above will not come into play when mocking interfaces since the getMock
method will return a mock class, but nonetheless a class, implementing the interface and the type check will pass.
PHPUnit cheat-sheet
I found one here and find it immensely useful.