I am trying to get into the good habit of testing my code, I did some exercise on the subject, but everything was so simple that, when I tried to apply what I learned on a real-world application I was extremely confused.
I'm writing a little PHP framework, using packages like Auryn, Zend-Diactoros and Fast-Route. I wanted to go heavy into interfaces to be able in future to swap dependencies with ease, so I wanted to write, for example, a RouterInterface that has a addRoute() and a match() methods, then what I just need to do is to write an adapter for the choosen package, to adapt it to my interface.
I wanted to use TDD to develop this framework, so I started with tests for my RouterAdapter class and I was totally lost. I can test a simple calculator class but how to test a class that depends heavily on a class that I don't really know how it works? I know that I have to store a RouteCollector object in order to store routes, I know that this object has some methods like getData() that returns an array of arrays, but how do I test a method like this one:
public function addRoute($methods, string $pattern, $handler)
{
$this->routeCollector->addRoute($methods, $pattern, $handler);
}
the class's constructor may be something like that
private $routeCollector;
public function __construct(RouteCollector $rc)
{
$this->routeCollector = $rc;
}
My test should check that in the routeCollector Object a route was added, but this requires to run checks on a private property inside my class. Maybe is easier than I thought, but right now I don't really have a valid solution to this, other than write utility methods to get the data stored in the object, something like this:
public function getRoutes()
{
/*
I want just the array in the first position because that array
is the array where are stored not-named routes.
*/
return $this->routeCollector->getData()[0];
}
Maybe I'm overthinking too much, this way of coding is really new for me
You can use a mocking framework in this case. The RouteCollector type which you pass into your constructor should ideally be an interface.
Then in your unit test you setup a mock implementation behind that interface. Or if you for some reason cannot pass an interface, then you can create a mock which is a subclass of RouteCollector. With typical mocking frameworks this mock object then can be setup to verify whether a certain method was called with certain parameters. In your example the method which would be setup is addRoute.
If you do not have a mocking framework then you have to create a mock implementation of RouteCollector on your own. You then have to implement some mechanism in your mock implementation to check whether certain methods were called (for example by setting some kind of flags) which you can then verify in your unit test.
What you should not do is exposing private fields in your classes just because a unit test needs them.
Let's use the Phake mocking framework (https://github.com/mlively/Phake) as an example.
I have never used this so I am writing this based on a quick reading of their documentation (http://phake.readthedocs.io/en/latest/method-verification.html). Here is a sample testcase.
public function testAddRoute()
{
// You want to see whether RouteCollector->addRoute is being called properly
// For this you have to create a RouteCollector mock and let it verify the call
$routeCollectorMock = Phake::mock('RouteCollector');
// Setup your test
$testCandidate = new YourClass($routeCollectorMock);
// Call the method you want to test
$testCandidate->addRoute($someMethods, $somePattern, $someHandler);
// Verify that RouteCollector->addRoute was called with your passed parameters
Phake::verify($routeCollectorMock)->addRoute($someMethods, $somePattern, $someHandler);
}