ClockMock: a library to mock date and time in PHP

If you write tests for your code (I hope you do), at some point you likely needed something to execute them with the system clock “frozen” to a specific date and time. Whoever had this need at least once knows that the matter is anything but trivial.

With this article, I want to tell the story of how we dealt with this problem at Slope. For doing this, I will use the following “codebase” (i.e. a pompous way to describe 2 classes — an entity and a service).

Our example codebase, using just vanilla PHP.

I will describe 3 testing scenarios (a, b. and c.) that pretty much cover 100% of our cases/needs. Your mileage may vary, but the same concepts should apply.

Our journey starts from a situation in which we simply accepted to test the following scenarios in a sub-optimal way — or to not test them at all.

Scenario a: alter the creation date of an entity when preparing fixtures for integration tests

Our “basic” go-to solution here was reflection, used to artificially modify the date (stored in a private instance property) right after calling the entity constructor. Example:

Scenario a. at the beginning of our story

Scenario b: test services that behave differently based on periods of the day/month/year

In such situations, we usually avoided to test these services altogether. In some other cases, we modified code to allow passing the current date as a parameter. We did not like making this kind of changes, because at that point you don’t know anymore whether the current date is effectively a parameter even in “production” usage, or it was made that way only for testing.

I’m not embedding any code for this scenario, because as the beginning it was simply untestable.

Scenario c: test services that set a specific date to an object with state, e.g. an entity

In the past, we worked around this by testing that the date set by the service was “close enough” to the current date (e.g. within 500 milliseconds), with the assumption that the assertion was made shortly after SUT code execution.

Scenario c. at the beginning of our story

It’s obvious that this kind of checks makes tests less precise, flaky and subject to failures in slow machines or environments with fluctuating computational resources (like in most CI environments).

As you are probably thinking, these solutions are pretty far from being ideal. Let’s be honest: they just suck. So, one day we decided we needed to make our codebase a better place and we started looking for alternatives.

Our first try: Carbon

As with every refactoring, we started with the lowest hanging fruit (in terms of complexity). We tried Carbon, a great library for handling dates that also allows mocking the current date used when you create Carbon|CarbonImmutable objects. In simple words, you can just do a Carbon::setTestNow($now) to freeze the current time (or use the stateless counterpart Carbon::withTestNow($now, $closure)).

As you would expect, our codebase needed some changes, and here they are:

Our example codebase, modified to use Carbon.

NOTE: I did not modify the use of DateTimeImmutable in Service::doSomething method, because I wanted to simulate use of 3rd party code outside of our control that thus cannot be changed to use Carbon.

After the codebase refactoring, we are immediately able to improve some of our scenarios:

Scenario a: alter the creation date of an entity when preparing fixtures for integration tests

Fixture preparation for scenario a. using Carbon

Scenario b: test services that behave differently based on periods of the day/month/year

Because we decided we could not use Carbon in Service::doSomething, we still cannot test that scenario.

Scenario c: test services that set a specific date to an object with state, e.g. an entity

Test for scenario c. using Carbon

Except the refactoring (that takes time, even though Carbon should pretty much be a drop-in replacement of DateTime), it was very easy to configure as it’s just a library you install with Composer.

There are a couple of drawbacks with this approach, though:

  • Any system function or class/method (e.g. date(), time(), DateTime, …) will still use the actual system clock, so it’s impossible to force code outside of your control to use your mocked date and time.
  • Your “business” code (entities and services) will have a hard dependency against Carbon (instead of sticking with the standard DateTime). This is not necessarily an issue, but it becomes relevant if you, like us, strive to keep exposure of your business logic to 3rd party libraries to the minimum.

ext/timecop to the (temporary) rescue

Due to the above limitations, at some point we decided we wanted to improve our situation and we came across ext/timecop. This is basically a fork of its most famous Ruby counterpart, and thus is based on the same concept: mock the system clock via a PHP runtime extension, so that all running code uses it transparently (regardless of the library).

It’s simple: you invoke \timecop_freeze($date) in your test code to bring the system clock to that specific moment. You then use \timecop_return() (still in your test code) when you want to go back to the actual system clock.

NOTE: from here on, we go back to referring to the initial codebase, the one without Carbon.

Scenario a: alter the creation date of an entity when preparing fixtures for integration tests

Fixture preparation for scenario a. using timecop

Scenario b: test services that behave differently based on periods of the day/month/year

Test for scenario b. using timecop

Scenario c: test services that set a specific date to an object with state, e.g. an entity

Test for scenario c. using timecop

Great! We can go back to using stdlib, uninstall Carbon, and still test everything.

The price you have to pay, is that you need to be able to compile and install a php extension yourself. This might not be possible when you don’t have full control on your environment, like in shared hosting spaces -even though I hope you’re not running production applications there!

Anyway, we were happy and life was good. At least, until the day to update to PHP 8.0 came. That day, we found out that ext/timecop would NOT compile anymore.

The day got even worse when, after going to the GitHub repo and looking at the issue tracker, we realized that the project was abandoned.

Timecop is dead. Long live ClockMock!

We really liked the concept behind timecop, but we were forced to move on if we didn’t want to be stuck with PHP 7.4 forever. As writing and maintaining a PHP extension was outside of our possibilities, we had to find a different solution.

One day, I stumbled on a not-so-popular PHP extension: ext/uopz. It allows to modify, at runtime, implementations of methods and functions, including the ones of the standard library. Kudos to its author Joe Watkins and contributors for the amazing job!

ClockMock does mainly 2 things whenever mocks are activated by the developer:

  1. Overwrites implementation of many date and time-related functions with ones that the current time can be manipulated.
  2. Overwrites implementation of \DateTime and \DateTimeImmutable with the ones of mock classes that consider the aforementioned mockable clock.

These implementations are reverted to their original versions whenever mocks are deactivated, so that there are no unintended side effects after using it.


DISCLAIMER: due to its “experimental” nature, we recommend to never use ClockMock in production.

You can use ClockMock with two different APIs:

  • a stateful one, in which you need to balance inline calls to activate and reset mocks (like timecop)
  • a stateless one, that will execute a closure at a specific point in time. It does not have side effects, as the original clock is always automatically restored.

Let’s see how the 3 scenarios can be rewritten with ClockMock:

Scenario a: alter the creation date of an entity when preparing fixtures for integration tests

Fixture preparation for scenario a. using ClockMock

Scenario b: test services that behave differently based on periods of the day/month/year

Test for scenario b. using ClockMock

Scenario c: test services that set a specific date to an object with state, e.g. an entity

Test for scenario c. using ClockMock

Possible alternative: inject a ClockInterface service

You might be thinking that a more “standard” way of solving this problem would be to inject a ClockInterface service that provides a way to obtain the current time (I also learned there’s a proposed standard for it). This way, the current time would be mockable like any other service.

This is definitely a valid approach. Problem is, in rather complex projects (not necessarily legacy) the current time is going to be needed in a wide range of places/classes. Furthermore, sometimes you may even need to get a \DateTime or a timestamp from a 3rd party library and that would “escape” your ClockInterface service (in case it’s impossible to make it interoperate with said library).

While it’s definitely doable to inject a Clock service in anything you manage using a DI container, it’s a lot less pragmatic to do the same for your value objects and/or entities.

When you need to create \DateTime objects right inside entities (in constructor or mutator methods) it would be cumbersome having to pass a Clock from the outside every time. Sometimes, use of dates could even be a private implementation detail — having to pass a clock service from the outside would leak this detail unnecessarily and make the public interface more complex.

In other words, the reason why we built this library is that we think it’s a good tradeoff to consider the current system time as a “global state”, and we prefer to avoid injecting a service to access it.

For this reason, I think that this injection-based approach only makes sense when you have valid reasons for doing it besides testing (e.g. very time-sensitive applications for which you need control over subtle details, like monotonic wall time, leap second smearing, etc…).

Our application is not time sensitive, we only needed a way to mock the system clock in tests. If your needs are the same and you can afford to use ClockMock, you can start using it with zero changes to production code.

Conclusions

As you can see from the examples above, using ClockMock feels pretty much the same as using timecop (with a bonus: the syntactic sugar of executeAtFrozenDateTime). It works with PHP 8 already, and the good thing is that the uopz extension is well maintained, so ClockMock is here to stay. We encourage you to use it and report any issues or feedback you may have.

The library is still incomplete, as mocks for some functions are missing. Just let us know if you want to help, contributions are welcome!

You can find the library on GitHub.

19