FakeTime

import { FakeTime } from "jsr:@ende/testing/time";

class

class FakeTime

Overrides the real Date object and timer functions with fake ones that can be controlled through the fake time instance.

Note: there is no setter for the start property, as it cannot be changed after initialization.

Example: Usage

import {
  assertSpyCalls,
  spy,
} from "@ende/testing/mock";
import { FakeTime } from "@ende/testing/time";

function secondInterval(cb: () => void) {
  return setInterval(cb, 1000);
}

Ende.test("secondInterval calls callback every second and stops after being cleared", () => {
  using time = new FakeTime();

  const cb = spy();
  const intervalId = secondInterval(cb);
  assertSpyCalls(cb, 0);
  time.tick(500);
  assertSpyCalls(cb, 0);
  time.tick(500);
  assertSpyCalls(cb, 1);
  time.tick(3500);
  assertSpyCalls(cb, 4);

  clearInterval(intervalId);
  time.tick(1000);
  assertSpyCalls(cb, 4);
});

Constructors

constructor

new FakeTime(start?: number | string | Date | null, options?: FakeTimeOptions)

Construct a FakeTime object. This overrides the real Date object and timer functions with fake ones that can be controlled through the fake time instance.

start?: number | string | Date | null

The time to simulate. The default is the current time.

options?: FakeTimeOptions

The options

Static methods

restore

static restore(): void

Restores real time.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals, assertNotEquals } from "@ende/assert"

const setTimeout = globalThis.setTimeout;

const fakeTime = new FakeTime();

assertNotEquals(globalThis.setTimeout, setTimeout);

FakeTime.restore();

assertEquals(globalThis.setTimeout, setTimeout);

restoreFor

static restoreFor<T>(callback: (...args: any[]) => Promise<T> | T, ...args: any[]): Promise<T>

Restores real time temporarily until callback returns and resolves.

callback: (...args: any[]) => Promise<T> | T

The callback to be called while FakeTime being restored

...args: any[]

The arguments to pass to the callback

Returns

The returned value from the callback

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals, assertNotEquals } from "@ende/assert"

const setTimeout = globalThis.setTimeout;

const fakeTime = new FakeTime();

assertNotEquals(globalThis.setTimeout, setTimeout);

FakeTime.restoreFor(() => {
  assertEquals(globalThis.setTimeout, setTimeout);
});

Methods

[Symbol.dispose]

[Symbol.dispose](): void

Restores real time.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals, assertNotEquals } from "@ende/assert";

const setTimeout = globalThis.setTimeout;

{
  using fakeTime = new FakeTime();

  assertNotEquals(globalThis.setTimeout, setTimeout);

  // test timer related things.

  // You don't need to call fakeTime.restore() explicitly
  // as it's implicitly called via the [Symbol.dispose] method
  // when declared with `using`.
}

assertEquals(globalThis.setTimeout, setTimeout);

now

get now(): number

The number of milliseconds elapsed since the epoch (January 1, 1970 00:00:00 UTC) for the fake time.

Returns

The number of milliseconds elapsed since the epoch (January 1, 1970 00:00:00 UTC) for the fake time

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

assertEquals(fakeTime.now, 15_000);

fakeTime.tick(5_000);

assertEquals(fakeTime.now, 20_000);
set now(value: number)

Set the current time. It will call any functions waiting to be called between the current and new fake time. If the timer callback throws, time will stop advancing forward beyond that timer.

value: number

The current time (in milliseconds)

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

assertEquals(fakeTime.now, 15_000);

fakeTime.now = 35_000;

assertEquals(fakeTime.now, 35_000);

start

get start(): number

The initial number of milliseconds elapsed since the epoch (January 1, 1970 00:00:00 UTC) for the fake time.

Returns

The initial number of milliseconds elapsed since the epoch (January 1, 1970 00:00:00 UTC) for the fake time.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

assertEquals(fakeTime.start, 15_000);

delay

delay(ms: number, options: DelayOptions = [UNSUPPORTED]): Promise<void>

Resolves after the given number of milliseconds using real time.

ms: number

The milliseconds to delay

options: DelayOptions = [UNSUPPORTED]

The options

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

await fakeTime.delay(500); // wait 500 ms in real time.

assertEquals(fakeTime.now, 15_000); // The simulated time doesn't advance.

runMicrotasks

runMicrotasks(): Promise<void>

Runs all pending microtasks.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assert } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

let called = false;

Promise.resolve().then(() => { called = true });

await fakeTime.runMicrotasks();

assert(called);

tick

tick(ms: number = 0): void

Adds the specified number of milliseconds to the fake time. This will call any functions waiting to be called between the current and new fake time.

ms: number = 0

The milliseconds to advance

Example: Usage

import {
  assertSpyCalls,
  spy,
} from "@ende/testing/mock";
import { FakeTime } from "@ende/testing/time";

function secondInterval(cb: () => void) {
  return setInterval(cb, 1000);
}

Ende.test("secondInterval calls callback every second and stops after being cleared", () => {
  using time = new FakeTime();

  const cb = spy();
  const intervalId = secondInterval(cb);
  assertSpyCalls(cb, 0);
  time.tick(500);
  assertSpyCalls(cb, 0);
  time.tick(500);
  assertSpyCalls(cb, 1);
  time.tick(3500);
  assertSpyCalls(cb, 4);

  clearInterval(intervalId);
  time.tick(1000);
  assertSpyCalls(cb, 4);
});

tickAsync

tickAsync(ms: number = 0): Promise<void>

Runs all pending microtasks then adds the specified number of milliseconds to the fake time. This will call any functions waiting to be called between the current and new fake time.

ms: number = 0

The milliseconds to advance

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assert, assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

let called = false;

Promise.resolve().then(() => { called = true });

await fakeTime.tickAsync(5_000);

assert(called);
assertEquals(fakeTime.now, 20_000);

next

next(): boolean

Advances time to when the next scheduled timer is due. If there are no pending timers, time will not be changed.

Returns

true when there is a scheduled timer and false when there is not.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assert, assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

let called = false;

setTimeout(() => { called = true }, 5000);

fakeTime.next();

assert(called);
assertEquals(fakeTime.now, 20_000);

nextAsync

nextAsync(): Promise<boolean>

Runs all pending microtasks then advances time to when the next scheduled timer is due. If there are no pending timers, time will not be changed.

Returns

true if the pending timers existed and the time advanced, false if there was no pending timer and the time didn't advance.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assert, assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

let called0 = false;
let called1 = false;

setTimeout(() => { called0 = true }, 5000);
Promise.resolve().then(() => { called1 = true });

await fakeTime.nextAsync();

assert(called0);
assert(called1);
assertEquals(fakeTime.now, 20_000);

runAll

runAll(): void

Advances time forward to the next due timer until there are no pending timers remaining. If the timers create additional timers, they will be run too. If there is an interval, time will keep advancing forward until the interval is cleared.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

let count = 0;

setTimeout(() => { count++ }, 5_000);
setTimeout(() => { count++ }, 15_000);
setTimeout(() => { count++ }, 35_000);

fakeTime.runAll();

assertEquals(count, 3);
assertEquals(fakeTime.now, 50_000);

runAllAsync

runAllAsync(): Promise<void>

Advances time forward to the next due timer until there are no pending timers remaining. If the timers create additional timers, they will be run too. If there is an interval, time will keep advancing forward until the interval is cleared. Runs all pending microtasks before each timer.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals } from "@ende/assert";

const fakeTime = new FakeTime(15_000);

let count = 0;

setTimeout(() => { count++ }, 5_000);
setTimeout(() => { count++ }, 15_000);
setTimeout(() => { count++ }, 35_000);
Promise.resolve().then(() => { count++ });

await fakeTime.runAllAsync();

assertEquals(count, 4);
assertEquals(fakeTime.now, 50_000);

restore

restore(): void

Restores time related global functions to their original state.

Example: Usage

import { FakeTime } from "@ende/testing/time";
import { assertEquals, assertNotEquals } from "@ende/assert";

const setTimeout = globalThis.setTimeout;

const fakeTime = new FakeTime(); // global timers are now faked

assertNotEquals(globalThis.setTimeout, setTimeout);

fakeTime.restore(); // timers are restored

assertEquals(globalThis.setTimeout, setTimeout);

Source