Did you ever write unit tests for code that dealt with date and time? Since time itself is out of your control, you might have used one of these approaches:

  • Test doubles, e.g. for the DateTime class in PHP. To make this work, DateTime or a factory thereof must be passed to the subject under test via dependency injection. My most popular blog post of all time is still “How to mock time() in PHPUnit” about mocking core functions like date() and time() if necessary (a hacky workaround, mind you)
  • Use derived values of the current time in fixtures and assertions, probably with some margin, e.g.
    assertEquals(\strtotime(“+1 day”), $cache->expiresAt(), “1”)
    where $cache->expiresAt() is the value we test and “1” is the allowed margin.

The latter is not very stable; it is likely that you run into edge cases where the tests are not deterministic. The former does not have this problem, but can result in a complicated setup of the test double.

Luckily, there is a third way, namely using a custom Clock (or Calendar) object that provides the current time and can easily be replaced with an (also custom) test double.

Here is a minimal interface for such a Clock object:

You may add methods to convert from and to date strings if you need them, but with this minimal interface you already have all you need if you do not need time in microseconds. From there, we can derive other date and time functions, e.g. the built-in functions date() and strtotime() can take an optional argument with the current timestamp.

The real implementation is just as simple:

Using the dependency injection framework of your choice, you can now depend on the “Clock” interface and pass a “SystemClock” instance by default.

For example, in Magento, “SystemClock” would be configured as “preference” for “Clock”, which works for any class that takes “Clock” as a constructor argument.

Usage example – a class that formats the current date:

The test double

For tests, we create a custom fake implementation instead of using a mocking framework. First, we need to make the current timestamp configurable:

In a test, we instantiate and pass the fake clock:

The fake clock above is nothing more than a stub that returns a configured value. But if treated carefully, it can evolve in a powerful tool: Every time you would call set() in your tests, think about the purpose and add a semantic method instead. For example, if you want to simulate time passing by, you can create a method like this:

And your test can turn from

to

which makes the intent immediately clear.

How these additional methods look like may depend heavily on your domain and the code under test. Do not hesitate to write very specific methods; a generic FakeClock class would not make much sense anyways. You would lose the benefit of explicit semantics in tests and gain the burden of another unnecessary dependency.

Some examples that may or may not be useful for you:

Using clock objects instead of built-ins like time and DateTime directly makes the dependency on an external recource (time) explicit. It does not mean that we should not use DateTime anymore, but if used for the current time, instead of

it should be

or add a convenient factory method to Clock and use it like this:

Summary

Custom Clock objects allows us to better deal with date and time in tests. They don’t make the client code overly complicated, give us full control, and with the right semantic methods in the fake implementation, make our tests much more expressive than it would be possible with a standard mock.

Fabian Schmengler

Author: Fabian Schmengler

Fabian Schmengler is Magento developer and trainer at integer_net. His focus lies in backend development, conceptual design and test automation.

Fabian was repeatedly selected as a Magento Master in 2017 and 2018 based on his engagements, active participation on StackExchange and contributions to the Magento 2 core.

More Information · Twitter · GitHub