Change the problem to write simpler tests.
Sometimes, writing tests for a piece of code is difficult but can be simplified by changing the problem we are trying to solve.
A very good example of this I know is about testing something time-related. Controlling time is something we cannot do. No matter what we try, time passes. We could use tools to control the system's clock and see where it goes, but we sometimes can find a simpler solution just by having another perspective about the problem we're trying to solve.
Imagine that you're working on a food inventory system that can tell if a dish is edible right now.
For instance, Panna Cottas can be eaten two days after they've been cooked. One possible way to implement that logic is the following code:
class PannaCotta {
public function __construct(
private readonly DateTimeImmutable $cookingDate
)
{}
public function isEdible(): bool {
$now = new DateTimeImmutable('now');
$twoDaysAfterCookingTime = $this->cookingDate->modify('+ 2 days');
return $now <= $twoDaysAfterCookingTime;
}
}
That code is pretty straightforward, but the dependency on time with the call to new DateTimeImmutable('now')
makes it difficult to test.
A test that ensures that a Panna Cotta cooked less than two days ago is edible will start failing at some point (in two days max, actually) if we hard-code the value of the cooking date. We could write our test to use a relative date, but they make tests harder to understand and can cause flakiness. That's not a satisfying solution either.
A better solution is to look for a solution to control the date used for the comparison, and this is where changing the problem can help us to write more straightforward test. Instead of trying to know if a panna cotta is edible right now, we can change the problem to ask if a panna cotta is edible at some date. After all, you might want to know if the panna cotta you have will still be edible tomorrow and, if so, choose something else for dessert right now.
Once we decide to move to that new problem, we can modify the PannaCotta
class code to this:
class PannaCotta {
public function __construct(
private readonly DateTimeImmutable $cookingDate
)
{}
public function isEdibleOn(DateTimeImmutable $potentialEatingDate): bool {
$twoDaysAfterCookingTime = $this->cookingDate->modify('+ 2 days');
return $potentialEatingDate <= $twoDaysAfterCookingTime;
}
}
We've renamed the isEdible
method to isEdibleOn
and have introduced a DateTimeImmutable
parameter representing the date we might eat the panna cotta, $potentialEatingDate
.
Testing that code is now a piece of cake, because we control the cooking date and the date at which we might eat the panna cotta:
class PannaCottaTest {
/**
* @test
*/
public function is_edible_up_to_two_days_after_cooking(
DateTimeImmutable $potentialEatingDate
): bool {
$pannaCotta = new PannaCotta(new DateTimeImmutable('2017-05-18'));
$this->assertTrue(
$pannaCotta->isEdibleOn(new DateTimeImmutable('20127-05-19')
);
}
}
As you can see, we didn't have to use some fancy tooling to create the test. Just a change of perspective and reframing the problem.
Do you speak French and want to stop hating your tests ?
I've created a course to help developers to improve their automated tests.
I share ideas and technics to improve slow, flaky, failing for unexpected reason and hard to understand tests until they become tests we take joy to work with !
But you'll need to understand French...
Dan North wrote an article about rethinking the problem we're trying to solve and discovering a missing concept in the domain you might be interested in as well. Funnily enough, it's also about time.
Forcing us to think harder about the problem is a really lovely side-effect of testing. When a test is difficult to write, seems odd to read, forces us to instantiate things we are not going to use anyway - something we can detect when we see a dummy (an article by me about that, in French)- it's a good clue that we should take a few minutes to rethink the problem or our design.
If you need some help with your test I think I can help. Have a look at my video course in French or let's have a chat and see what we can do together.