Mocking Current Time and Sleep in PHP
If you are trying to test logic that relies on the current time you have probably run into at least one of these issues:
- Time is always moving. Predicating a result, such as a hash may be difficult or impossible.
- Tests become brittle. If your production code or test needs to refer to the current time it may read before/after the second ticks over. This means that every so often the test will fail. I have also seen tests that were passing for months that suddenly start failing because of the change in daylight saving time.
- Some tests need to verify if something specific has or has not changed when the some period of time passes. The easiest choice is to add a
sleep()
to your test, but that really slows down your tests.
There are some less than desirable ways around this. However, it really comes back the the principle that your output should be deterministic from your input. Dependency injectionis a great way to control this and create testable code. The system clock is actually just a dependency like any other.
That is to say that the logic shouldn’t need to know where it’s getting the current time from so long as it can get it.
Jumping right into an example:
class TimeFormatter
{
public function whatTimeIsIt()
{
return (new DateTime())->format(DateTimeInterface::ISO8601);
}
}
Obviously the test would fail:
class TimeFormatterTest
{
public function testWhatTimeIsIt()
{
$timeFormatter = new TimeFormatter(); $actual = $timeFormatter->whatTimeIsIt();
$this->assertEquals("2018-08-15T15:52:01+0000", $actual); sleep(60); $actual = $timeFormatter->whatTimeIsIt();
$this->assertEquals("2018-08-15T15:53:01+0000", $actual);
}
}
Let’s consider the clock as an interface:
interface ClockInterface
{
/**
* Get the current time.
*/
public function now(): DateTime; /**
* Sleep for a fixed number of whole seconds.
*
* This can be used as a substitution of sleep().
*/
public function sleep(int $seconds): void;
}
Now updating our class to abstract away the clock as a dependency:
class TimeFormatter
{
private $clock; public function __construct(ClockInterface $clock)
{
$this->clock = $clock;
} public function whatsTheTime()
{
return $this->clock->now()->format(DateTimeInterface::ISO8601);
}
}
This may look a bit alien at first. However, remember that the clock is still actually a dependency, even if you are not used to handling it in this way.
Now the test looks like this:
class TimeFormatterTest
{
public function testWhatTimeIsIt()
{
$clock = new FakeClock();
$timeFormatter = new TimeFormatter($clock); $actual = $timeFormatter->whatTimeIsIt();
$this->assertEquals("2018-08-15T15:52:01+0000", $actual); $clock->sleep(60); $actual = $timeFormatter->whatTimeIsIt();
$this->assertEquals("2018-08-15T15:53:01+0000", $actual);
}
}
Where did FakeClock
come from? Well here it is:
/**
* FakeClock is used for testing.
*
* An instance will always have a starting value of
* "Wed, 04 Apr 1984 00:00:00 +0000".
*/
class FakeClock implements ClockInterface
{
private $time; public function __construct()
{
// 449884800 = "Wed, 04 Apr 1984 00:00:00 +0000"
$this->time = DateTime::createFromFormat('U', 449884800);
} public function now(): DateTime
{
return $this->time;
} public function sleep(int $seconds): void
{
$this->time->add(new DateInterval("PT{$seconds}S"));
}
}
Great. Not only have we made the clock predictable, but sleep is now instantaneous.
In production code we can use the RealClock
:
class RealClock implements ClockInterface
{
public function now(): DateTime
{
return new DateTime();
} public function sleep(int $seconds): void
{
sleep($seconds);
}
}
The interface and implementation were inspired from jonboulle/clockwork which I have used with Go.
Originally published at http://elliot.land on November 20, 2018.