Choosing Between jasmine.clock() and Real Timers in Unit Tests
A decision guide for Jasmine timer tests: when to use jasmine.clock() for deterministic, instant specs versus real timers, with a concrete install/tick/uninstall example and a validation spec.
03 Sept 2026, 15:17 UTC

The decision: mock the clock or wait for real time
If your unit under test uses setTimeout or setInterval, you have two supported ways to test it in Jasmine: let real time pass (with done or async/await), or install Jasmine's mock clock with jasmine.clock().install() and advance time manually with tick(). The wrong choice shows up as flaky CI runs or suites that take minutes to crawl through artificial delays.
The short version: use the mock clock whenever the test only cares that timer logic fires correctly, and use real timers only when the behavior genuinely depends on wall-clock time or on APIs the mock does not intercept.
Comparing the two options
| Concern | jasmine.clock() | Real timers (done/async) |
|---|---|---|
| Determinism | Fully deterministic; time advances only when you call tick() | Subject to scheduler jitter; flaky under load |
| Suite speed | Instant, regardless of delay values | Test waits for the real delay to elapse |
| Coverage of timer APIs | setTimeout, setInterval, clearTimeout, clearInterval, Date | Everything, including requestAnimationFrame and I/O-bound waits |
| Setup discipline | Must install before timers are scheduled and uninstall after | No setup; just signal completion |
| What it actually proves | That your code schedules and reacts to timers correctly | That the code works against the real event loop |
Trade-offs worth knowing before you commit
The mock clock makes asynchronous timer code behave synchronously, which is exactly what removes flakiness. But it has sharp edges. First, installation order matters: only timers scheduled after install() are intercepted. A timer your application code already scheduled keeps running on the real clock, which produces confusing half-mocked behavior.
Second, the mock does not capture non-standard scheduling APIs. Code that waits on requestAnimationFrame, MutationObserver, or Promise microtasks will not be advanced by tick(). If your component debounces via setTimeout but animates via requestAnimationFrame, the mock only covers half of it.
Third, forgetting uninstall() leaks the mock into later specs. Whatever runs next sees a frozen clock, and the failure appears in an unrelated test. Put install/uninstall in beforeEach/afterEach so the pairing is structural, not remembered.
Real timers keep their place for integration-style specs: verifying that a polling loop actually terminates, or that a timeout races against a real network stub. Keep those few and mark them clearly, because they are the specs most likely to flake.
Concrete implementation
This example assumes Jasmine 3.x or later in a browser or Node environment where jasmine.clock() is available. Run it with your usual Jasmine runner (npx jasmine for Node, or your Karma/browser setup); no special permissions are needed.
describe('session timeout warning', () => {
let warningShown;
const startWarningTimer = () => {
setTimeout(() => { warningShown = true; }, 30 * 1000);
};
beforeEach(() => {
warningShown = false;
jasmine.clock().install(); // BEFORE any timer is scheduled
});
afterEach(() => {
jasmine.clock().uninstall(); // always restore the real clock
});
it('shows the warning after 30 seconds', () => {
startWarningTimer();
expect(warningShown).toBeFalse(); // nothing yet
jasmine.clock().tick(30 * 1000); // advance 30s instantly
expect(warningShown).toBeTrue();
});
it('does not show the warning early', () => {
startWarningTimer();
jasmine.clock().tick(29 * 1000);
expect(warningShown).toBeFalse();
});
});The key check is the negative assertion before tick(): it proves the callback has not fired early, which is the assertion real-timer tests can only approximate with generous margins.
Validating that the mock is actually in control
A quick sanity spec confirms your environment supports the clock and that install/uninstall behave as expected:
it('mock clock controls setTimeout', () => {
jasmine.clock().install();
let fired = false;
setTimeout(() => { fired = true; }, 1000);
jasmine.clock().tick(1000);
expect(fired).toBeTrue(); // fired synchronously
jasmine.clock().uninstall();
});
it('real timers resume after uninstall', (done) => {
const start = Date.now();
setTimeout(() => {
expect(Date.now() - start).toBeGreaterThanOrEqual(45);
done();
}, 50);
});If the first spec passes, the mock intercepted the timer. If the second passes, the environment was restored. If the first spec hangs or the callback never fires, check that install() ran before the timer was scheduled and that your Jasmine version supports the clock API.
Limitations to keep in mind
- Timers scheduled before
install()are not mocked and will still fire on real time. requestAnimationFrameand other non-standard schedulers are outside the mock's reach.- Node and browser timer implementations differ slightly; verify the sanity spec above passes in your target environment before converting a whole suite.
- The mock proves scheduling logic, not real-world timing. Keep at least one real-timer test for anything where elapsed wall time is the actual requirement.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.