Every (or at least a lot of) project seems to have code like this somewhere:
if (user.subscriptionEndsAt < new Date()) {
// …
}
There’s almost nothing wrong with it until tests come.
The result is you will end up freezing time, mocking Date, enabling fake timers, remembering to restore them afterwards, and hoping another test didn’t leave the clock in a weird state.
While Jest’s and Sinon/Vitest’s fake timers are great tools, they always felt like they were solving the problem from the outside by patching global APIs, which is not the greatest DDD pattern.
What about treating time as a dependency ?
.NET is doing it since version 8.
When you think about it, time isn’t much different from a database or an HTTP client from a business logic : your code depends on it, but it doesn’t have to know where it comes from.
Instead of writing this:
const now = new Date();
what if we wrote this?
const now = timeProvider.clock.utcNow();
Your code is now testable because you rely on a provider for your time.
Why not a classic IClock interface then ? Because that could typically been factored out, precisely because it’s always the same tiny logic here : abstract time reading behind an interface and inject a fake one in tests. That’s what Time-Provider does.
Actually it goes beyond this because :
- You keep your favourite Date library (via plugging it through the plugin interface).
- You have timers (timeout/interval) abstractions at no costs (with 3 clocks strategies in deterministic mode) + a helper to chain and rearms setTimeout calls with a fluctuating delay (aka. dynamic intervals).
- Performance api is also there.
- And if you need the request animation frame API for browsers, it’s also there through an add-on (because it’s browser specific).
At this point you don’t need global fake timers anymore. You just rely on the time-provider system pass-through for your production code and uses the deterministic version for your tests. And Time-Provider is great for this because it’s tree-shakable : Your production code is never bloated with deterministic code.
This is the purpose of @time-provider/core.
Thanks to it’s add-on architecture you can easily extend, with additional features. Cron and ETA are next to come.
The result is you only import what you need. Your code stay small and testable.
I’d love to hear how other JavaScript or TypeScript developers approach this.
If you’re interested, the project is here:
https://jaenyf.github.io/time-provider/
Feedback, criticism, and/or alternative approaches are all welcome.
답글 남기기