Skip to content

Commit ceea584

Browse files
test(daemon): use fake timers and mock date to test daemon
Make tests more predictable by not actually having to wait a real amount of time.
1 parent 0e8df71 commit ceea584

File tree

4 files changed

+92
-20
lines changed

4 files changed

+92
-20
lines changed

CronDaemon.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { parse, Schedule } from "./parse";
22
import { next } from "./next";
33

4-
// When delay is larger than 2147483647 or less than 1, the delay will be set to 1.
5-
const MAX_DELAY = Math.pow(2, 32 - 1) - 1;
4+
// When delay is larger than `(2^31) - 1)` the delay will be set to 1.
5+
const MAX_DELAY = 2147483647;
66

77
/**
88
* Calculate the next time to execute the job.

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@
4444
"babel-jest": "^26.6.1",
4545
"commitizen": "^4.2.2",
4646
"jest": "^26.6.1",
47+
"mockdate": "^3.0.2",
4748
"rimraf": "^3.0.2",
4849
"semantic-release": "^17.2.2",
4950
"typedoc": "^0.19.2",
5051
"typescript": "^4.0.5"
52+
},
53+
"jest": {
54+
"timers": "modern"
5155
}
5256
}

tests/CronDaemon.test.ts

+81-18
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,143 @@
1+
import MockDate from "mockdate";
12
import { CronDaemon } from "../CronDaemon";
23

3-
const waitFor = (ms: number) =>
4-
new Promise((resolve, reject) => {
5-
setTimeout(resolve, ms);
6-
});
4+
let daemon: CronDaemon | undefined;
5+
6+
beforeAll(() => {
7+
MockDate.set(Date.now());
8+
});
9+
afterEach(() => {
10+
if (daemon) {
11+
daemon.stop();
12+
daemon = undefined;
13+
}
14+
});
15+
afterAll(() => {
16+
MockDate.reset();
17+
});
718

819
test("triggering", async () => {
920
const callback = jest.fn();
10-
const daemon = new CronDaemon("* * * * * * *", callback);
21+
daemon = new CronDaemon("* * * * * * *", callback);
22+
1123
expect(daemon.state()).toEqual("running");
1224
expect(callback).not.toHaveBeenCalled();
13-
await waitFor(1000);
25+
26+
MockDate.set(Date.now() + 1000);
27+
jest.advanceTimersByTime(1000);
28+
1429
expect(callback).toHaveBeenCalledTimes(1);
1530
});
1631

1732
test("schedule without next date", async () => {
1833
const callback = jest.fn();
19-
const daemon = new CronDaemon("* * * * * * 1980", callback);
34+
daemon = new CronDaemon("* * * * * * 1980", callback);
35+
2036
expect(daemon.next()).toBeUndefined();
21-
await waitFor(1000);
37+
38+
MockDate.set(Date.now() + 1000);
39+
jest.advanceTimersByTime(1000);
40+
2241
expect(callback).not.toHaveBeenCalled();
2342
});
2443

2544
test("calling start multiple times", async () => {
2645
const callback = jest.fn();
27-
const daemon = new CronDaemon("* * * * * * *", callback);
46+
daemon = new CronDaemon("* * * * * * *", callback);
47+
2848
daemon.start();
2949
daemon.start();
50+
3051
expect(daemon.state()).toEqual("running");
3152
expect(callback).not.toHaveBeenCalled();
32-
await waitFor(1000);
53+
54+
MockDate.set(Date.now() + 1000);
55+
jest.advanceTimersByTime(1000);
56+
3357
expect(callback).toHaveBeenCalledTimes(1);
3458
});
3559

3660
test("stopping", async () => {
3761
const callback = jest.fn();
38-
const daemon = new CronDaemon("* * * * * * *", callback);
62+
daemon = new CronDaemon("* * * * * * *", callback);
63+
3964
daemon.stop();
65+
4066
expect(daemon.state()).toEqual("stopped");
41-
await waitFor(1000);
67+
68+
MockDate.set(Date.now() + 1000);
69+
jest.advanceTimersByTime(1000);
70+
4271
expect(callback).not.toHaveBeenCalled();
4372
});
4473

4574
test("doesn't break when next trigger is a long way away", async () => {
4675
const callback = jest.fn();
47-
const daemon = new CronDaemon("* * * * * * 2099", callback);
76+
daemon = new CronDaemon("* * * * * * 2099", callback);
77+
4878
expect(daemon.state()).toEqual("running");
49-
await waitFor(50);
79+
80+
MockDate.set(Date.now() + 50);
81+
jest.advanceTimersByTime(50);
82+
5083
expect(callback).not.toHaveBeenCalled();
5184
});
5285

5386
test("can call stop multiple times", async () => {
5487
const callback = jest.fn();
55-
const daemon = new CronDaemon("* * * * * * *", callback);
88+
daemon = new CronDaemon("* * * * * * *", callback);
89+
5690
daemon.stop();
5791
daemon.stop();
92+
5893
expect(daemon.state()).toEqual("stopped");
5994
expect(callback).not.toHaveBeenCalled();
6095
});
6196

6297
test("custom date function", async () => {
6398
const callback = jest.fn();
64-
const daemon = new CronDaemon((now) => {
99+
daemon = new CronDaemon((now) => {
65100
const next = new Date(now);
66101
next.setTime(now.getTime() + 10);
67102
return next;
68103
}, callback);
69-
await waitFor(20);
104+
105+
MockDate.set(Date.now() + 50);
106+
jest.advanceTimersByTime(20);
107+
70108
expect(callback).toHaveBeenCalled();
71109
});
72110

73111
test("Invalid schedule type causes an error", () => {
112+
const callback = jest.fn();
74113
try {
75-
new CronDaemon({} as any, jest.fn());
114+
daemon = new CronDaemon({} as any, callback);
76115
throw new Error("Should have failed validation");
77116
} catch (error) {
78117
expect(error.message).toEqual("Invalid schedule type");
118+
expect(callback).not.toHaveBeenCalled();
79119
}
80120
});
121+
122+
test("Very long delay", () => {
123+
const maxDelay = Math.pow(2, 32 - 1) - 1;
124+
const requestedDelay = maxDelay * 1.5;
125+
const start = Date.now();
126+
MockDate.set(start);
127+
128+
const callback = jest.fn();
129+
daemon = new CronDaemon(
130+
(now) => new Date(now.getTime() + requestedDelay),
131+
callback
132+
);
133+
134+
jest.runOnlyPendingTimers();
135+
MockDate.set(start + maxDelay);
136+
137+
expect(callback).not.toHaveBeenCalled();
138+
139+
MockDate.set(start + requestedDelay);
140+
jest.runOnlyPendingTimers();
141+
142+
expect(callback).toHaveBeenCalledTimes(1);
143+
});

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -5596,6 +5596,11 @@ mkdirp@~1.0.3:
55965596
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
55975597
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
55985598

5599+
mockdate@^3.0.2:
5600+
version "3.0.2"
5601+
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.2.tgz#a5a7bb5820da617747af424d7a4dcb22c6c03d79"
5602+
integrity sha512-ldfYSUW1ocqSHGTK6rrODUiqAFPGAg0xaHqYJ5tvj1hQyFsjuHpuWgWFTZWwDVlzougN/s2/mhDr8r5nY5xDpA==
5603+
55995604
modify-values@^1.0.0:
56005605
version "1.0.1"
56015606
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"

0 commit comments

Comments
 (0)