Skip to content

Commit 9a384b3

Browse files
authored
feat: Add ability to delay generating a response (#28)
* feat: Add ability to delay generating a response * Fix error message * Update error message * Fix delay test
1 parent b314c31 commit 9a384b3

File tree

6 files changed

+79
-15
lines changed

6 files changed

+79
-15
lines changed

docs/src/content/docs/mock-servers/extended-response-patterns.mdx

+18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The following keys can be used in the response pattern object:
1212
- `status` (required) - the HTTP status code to return.
1313
- `headers` - HTTP headers to return with the response.
1414
- `body` - the body of the response to return. This can be a string, an object (which is treated as JSON), or an `ArrayBuffer`.
15+
- `delay` - the number of milliseconds to wait before returning the response.
1516

1617
The response pattern keys are used to create a new `Response` object that is returned when the associated request pattern matches the request.
1718

@@ -96,3 +97,20 @@ server.get("/users", {
9697
```
9798

9899
This route will respond to any GET request to `/users` with a status code of 200, a `Content-Type` header of `application/json`, and a JSON response body containing an `id` and `name` property.
100+
101+
## Delaying the response
102+
103+
If you'd like to delay the response to a request, you can use the `delay` key in the response pattern object. The `delay` property is the number of milliseconds to wait before returning the response. Here's an example:
104+
105+
```js
106+
import { MockServer } from "mentoss";
107+
108+
const server = new MockServer("https://api.example.com");
109+
110+
server.get("/users", {
111+
status: 200,
112+
delay: 1000, // delay the response by 1 second
113+
});
114+
```
115+
116+
This route will respond to any GET request to `/users` with a status code of 200, but it will wait for 1 second before returning the response.

src/fetch-mocker.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
//-----------------------------------------------------------------------------
2424

2525
/** @typedef {import("./types.js").RequestPattern} RequestPattern */
26-
/** @typedef {import("./types.js").ResponsePattern} ResponsePattern */
2726
/** @typedef {import("./mock-server.js").MockServer} MockServer */
2827
/** @typedef {import("./mock-server.js").Trace} Trace */
2928

@@ -176,11 +175,11 @@ export class FetchMocker {
176175
// first check to see if the request has been aborted
177176
const signal = init?.signal;
178177
signal?.throwIfAborted();
179-
180-
// assign an event handler to listen for abort events
181-
signal?.addEventListener("abort", () => {
182-
throw signal?.reason ?? new Error("Fetch aborted");
183-
});
178+
179+
// TODO: For some reason this causes Mocha tests to fail with "multiple done"
180+
// signal?.addEventListener("abort", () => {
181+
// throw new Error("Fetch was aborted.");
182+
// });
184183

185184
// adjust any relative URLs
186185
const fixedInput =
@@ -216,11 +215,15 @@ export class FetchMocker {
216215
}
217216
}
218217

218+
signal?.throwIfAborted();
219+
219220
const response = await this.#internalFetch(request, init?.body);
220221

221222
if (useCors && this.#baseUrl) {
222223
assertCorsResponse(response, this.#baseUrl.origin);
223224
}
225+
226+
signal?.throwIfAborted();
224227

225228
return response;
226229
};

src/mock-server.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @author Nicholas C. Zakas
44
*/
55

6-
/* global Response, FormData */
6+
/* global Response, FormData, setTimeout */
77

88
//-----------------------------------------------------------------------------
99
// Imports
@@ -232,6 +232,16 @@ export class Route {
232232
},
233233
});
234234
}
235+
236+
/**
237+
* Creates a delay as specified by the route's response pattern.
238+
* @returns {Promise<void>} A promise that resolves when the delay is over.
239+
*/
240+
async delay() {
241+
if (this.#response.delay) {
242+
await new Promise(resolve => setTimeout(resolve, this.#response.delay));
243+
}
244+
}
235245

236246
/**
237247
* Returns a string representation of the route.
@@ -443,6 +453,8 @@ export class MockServer {
443453
*/
444454
const response = route.createResponse(PreferredResponse);
445455
Object.defineProperty(response, "url", { value: request.url });
456+
457+
await route.delay();
446458

447459
return { response, traces };
448460
}

src/types.ts

+17
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,24 @@ export interface RequestPattern {
1717
export type MethodlessRequestPattern = Omit<RequestPattern, "method">;
1818

1919
export interface ResponsePattern {
20+
21+
/**
22+
* The status code of the response.
23+
*/
2024
status: number;
25+
26+
/**
27+
* The headers of the response.
28+
*/
2129
headers?: Record<string, string>;
30+
31+
/**
32+
* The body of the response.
33+
*/
2234
body?: string | any | ArrayBuffer | null;
35+
36+
/**
37+
* The number of milliseconds to delay the response by.
38+
*/
39+
delay?: number;
2340
}

tests/fetch-mocker.test.js

+6-8
Original file line numberDiff line numberDiff line change
@@ -1039,8 +1039,7 @@ describe("FetchMocker", () => {
10391039
);
10401040
});
10411041

1042-
// need to have delayed responses for this to work
1043-
it.skip("should throw an abort error when the request is aborted", async () => {
1042+
it("should throw an abort error when the request is aborted", async () => {
10441043
const server = new MockServer(BASE_URL);
10451044
const fetchMocker = new FetchMocker({
10461045
servers: [server],
@@ -1049,19 +1048,18 @@ describe("FetchMocker", () => {
10491048
server.get("/hello", {
10501049
status: 200,
10511050
body: "Hello world!",
1051+
delay: 100
10521052
});
10531053

10541054
const controller = new AbortController();
10551055

10561056
queueMicrotask(() => controller.abort());
10571057

1058-
const request = fetchMocker.fetch(BASE_URL + "/hello", {
1059-
signal: controller.signal,
1060-
});
1061-
10621058
await assert.rejects(
1063-
request,
1064-
/AbortError/,
1059+
fetchMocker.fetch(BASE_URL + "/hello", {
1060+
signal: controller.signal,
1061+
}),
1062+
/aborted/,
10651063
);
10661064
});
10671065

tests/mock-server.test.js

+16
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,22 @@ describe("MockServer", () => {
248248
const response = await server.receive(request);
249249
assert.strictEqual(response.url, `${BASE_URL}/test?foo=bar`);
250250
});
251+
252+
it("should delay the response by at least 500ms", async () => {
253+
server.get("/test", { status: 200, body: "OK", delay: 500 });
254+
255+
const request = createRequest({
256+
method: "GET",
257+
url: `${BASE_URL}/test?foo=bar`,
258+
});
259+
260+
const startTime = Date.now();
261+
const response = await server.receive(request);
262+
const elapsed = Date.now() - startTime;
263+
264+
assert.ok(elapsed >= 500, `Response was delayed ${elapsed}ms, expected at least 500ms.`);
265+
assert.strictEqual(response.url, `${BASE_URL}/test?foo=bar`);
266+
});
251267
});
252268

253269
describe("traceReceive()", () => {

0 commit comments

Comments
 (0)