Time traveling in Swift unit tests
A lot of code that we write relies on the current date in some way. Whether it’s cache invalidation, handling time sensitive data, or keeping track of durations, we usually simply perform comparisons against
Date() — for example using
However, writing tests against code that uses such date comparisons can sometimes be a bit tricky. If the intervals are small enough, you could simply add waiting time to your tests (although that’s really not recommended, since it’ll slow them down, and is a common source of flakiness) — but if we’re talking about hours or days in between our dates, that’s simply not possible.
This week, let’s take a look at how to test code that relies on dates in a simple and fun way — using “time traveling” 😀
Consider the following
Cache class, that simply provides APIs for caching and retrieving cached objects:
As you can see above, we calculate an
endDate for each cache entry, based on the current date, offset by 24 hours (in seconds). So how do we test this class in a predictable and efficient way?
Let’s start by applying a technique from “Simple Swift dependency injection with functions” and make it possible to inject a function (we’ll call it
dateGenerator) that generates the current date, instead of calling
Date() directly. This will enable us to easily mock the current date in our tests.
The implementation of
Cache now looks like this:
One thing to note above is that we keep
Date.init as the default date generator, to enable
Cache to be initialized without any arguments in our production code — just like before 👍
Now, let’s do some time traveling! In order to verify that our
Cache correctly discards outdated entries, we’re going to need to:
- Add an object to the cache.
- Verify that the object is indeed cached.
- Time travel 24 hours into the future.
- Verify that the object is no longer cached.
To enable the time traveling part, let’s implement a
TimeTraveler class (that will only be part of our testing target), which will enable us to move in time using a given time interval:
Finally, let’s write a test for
Cache that uses the
generateDate() method of a
TimeTraveler instance as its date generator:
That’s it! 🎉 We now have a fast & predictable date-dependent test, without having to invent a lot of infrastructure or resort to hacky solutions like swizzling the system date.
Do you have questions, comments or suggestions for upcoming weekly blog posts? I’d love to hear from you! 😊 Contact me on Twitter @johnsundell.
Thanks for reading! 🚀