Improve your PHP code testability
Welcome to my article! I hope you are like me and believe in the power of unit tests, or at least know that it’s important to write them, right? In short words, I like them because it gives me more confidence that my code is working and no one else in the future will make changes there by mistake.
In this article I want to show you how to combine mocks with reflection and write a test that seemed impossible before.
Final code is on Github.
I’ve just started my new job, in this company that uses PHP as a main language. Got a new ticket and I was excited to ship my new code (with tests, of course!).
But suddenly, a wild class appears! It doesn’t expose one dependency that I needed to mock. I could refactor this code to receive this dependency by its constructor but decided to not follow this path because I was new (noob) at the company, so I didn’t know if this was a common pattern, or what the original developer of this class was thinking when made this design.
The other alternative that I’m going to show here later was not supposed to be a big effort. Coming from a Java background, I knew this could be done using reflection.
Before jumping into code, we need to understand the concept of reflection programming. Here goes my simple definition:
Reflection is a tool that can be used to inspect your code and bypass static typing.
For example, in this article I will use reflection to change an instance of an unacessible attribute from a class that doesn’t have a setter or another way to expose this attribute.
Why would you want to do this?
In this example, the
Implementation class has a
public method that calls a method from the unacessible dependency. So the test we want to write is this: is the dependency being called with correct arguments when
public method from
Show me the code
abstract class AbstractClass
public function __construct()
$this->unacessibleDependency = new UnacessibleDependency();
abstract function doSomething(string $argument): void;
As you can see, there’s no common way we can access
$unacessibleDependency from outside, unless we refactor this class, which I already said we don't want to follow this path for several reasons.
class ImplementationClass extends AbstractClass
public function doSomething(string $argument): void
doSomething is the method we want to test.
Start by creating an instance of the system under test (SUT):
$this->implementation = new ImplementationClass();
unacessibleDependency of the previous instance accessible by reflection:
$reflectionClass = new ReflectionClass($this->implementation);
$property = $reflectionClass->getProperty('unacessibleDependency');
Create a mock instance of
UnacessibleDependency so we can verify if it's being called:
$this->mockUnacessible = $this->createMock(UnacessibleDependency::class);
Then substitute the original depedency by the mock one:
Finally, the assertion
Translating to human language: when
doSomething is called with
argument parameter, is
doSomethingElse being called with the same parameter?
Now we have our test, and no one got hurt during this process.
There’s a way to add a unit test even in unlikely scenarios, but this technique should be used with caution, because we may be just adding an workaround to a poorly code design. If the code is not easily testable, it’s probably not following the good practices of encapsulation and abstraction.
I want to know more!
For more techniques to make your code more testable, check out the book “Working Effectively with Legacy Code” by Michael C. Feathers.