Skip to content
AB

Deterministic Tests by Injecting Time and IDs

Replace hidden calls to the clock and random UUID generator with tiny dependencies that make Jest tests exact and repeatable.

Ashish BhakhandPublished

Time and random IDs look harmless until a test needs an exact result. Calling new Date() and crypto.randomUUID() inside business logic introduces two invisible inputs.

Code
export type Clock = () => Date;
export type IdGenerator = () => string;

type Dependencies = {
  clock: Clock;
  generateId: IdGenerator;
  reminders: ReminderRepository;
};

The use case receives them like any other dependency:

Code
export const scheduleReminder =
  (dependencies: Dependencies) => async (userId: string, message: string) => {
    const createdAt = dependencies.clock();
    const reminder = {
      id: dependencies.generateId(),
      userId,
      message,
      createdAt,
      sendAt: new Date(createdAt.getTime() + 60 * 60 * 1000),
    };

    await dependencies.reminders.add(reminder);
    return reminder;
  };

The test controls reality#

Code
test("schedules the reminder one hour later", async () => {
  const add = jest.fn();
  const run = scheduleReminder({
    clock: () => new Date("2026-01-10T09:00:00.000Z"),
    generateId: () => "reminder-123",
    reminders: { add },
  });

  await run("user-1", "Stand up");

  expect(add).toHaveBeenCalledWith({
    id: "reminder-123",
    userId: "user-1",
    message: "Stand up",
    createdAt: new Date("2026-01-10T09:00:00.000Z"),
    sendAt: new Date("2026-01-10T10:00:00.000Z"),
  });
});

No fuzzy time comparison, global fake timer, or UUID matcher is required.

Production wiring remains simple:

Code
const run = scheduleReminder({
  clock: () => new Date(),
  generateId: () => crypto.randomUUID(),
  reminders: mongoReminderRepository,
});

Inject only nondeterminism that affects behavior. Wrapping every language function creates noise. Clocks, ID generators, randomness, and sometimes retry delays are strong candidates because tests need to control them.

This technique also exposes domain questions. Should expiration use request-start time or write time? Is an ID allocated before validation? Once time and identity are explicit inputs, those choices stop hiding inside implementation details.

← Back to all writing