Skip to content

Commit c05baa2

Browse files
authored
feat: Add test helper methods (#33)
1 parent 4a430b0 commit c05baa2

File tree

6 files changed

+557
-33
lines changed

6 files changed

+557
-33
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
---
2+
title: "Testing Helpers: Fetch Mocker"
3+
description: The fetch mocker methods that are helpful for testing.
4+
---
5+
6+
import { Aside } from "@astrojs/starlight/components";
7+
8+
The fetch mocker provides several methods to help verify that your requests were made as expected.
9+
10+
## Verifying Route Calls
11+
12+
Tracking which routes have been called is a key part of many tests. The fetch mocker provides several methods and properties to help with this.
13+
14+
### `allRoutesCalled()`
15+
16+
The `allRoutesCalled()` method checks if all routes have been called on all servers. It returns `true` if all routes have been called at least once, and `false` otherwise:
17+
18+
```js {10, 13, 16}
19+
import { MockServer, FetchMocker } from "mentoss";
20+
21+
const server1 = new MockServer("https://api1.example.com");
22+
const server2 = new MockServer("https://api2.example.com");
23+
const mocker = new FetchMocker({ servers: [server1, server2] });
24+
25+
server1.get("/users", 200);
26+
server2.post("/users", 200);
27+
28+
console.log(mocker.allRoutesCalled()); // false
29+
30+
await mocker.fetch("https://api1.example.com/users");
31+
console.log(mocker.allRoutesCalled()); // false
32+
33+
await mocker.fetch("https://api2.example.com/users", { method: "POST" });
34+
console.log(mocker.allRoutesCalled()); // true
35+
```
36+
37+
In this example, the `allRoutesCalled()` method first returns `false` because neither the GET nor the POST `/users` route has been called yet. After both routes have been called, the method returns `true`.
38+
39+
### `assertAllRoutesCalled()`
40+
41+
The `assertAllRoutesCalled()` method is an assertion that checks if all routes have been called. It throws an error if any routes have not been called:
42+
43+
```js {11}
44+
import { MockServer, FetchMocker } from "mentoss";
45+
46+
const server1 = new MockServer("https://api1.example.com");
47+
const server2 = new MockServer("https://api2.example.com");
48+
const mocker = new FetchMocker({ servers: [server1, server2] });
49+
50+
server1.get("/users", 200);
51+
server2.post("/users", 200);
52+
53+
await mocker.fetch("https://api1.example.com/users");
54+
server.assertAllRoutesCalled(); // Error!
55+
```
56+
57+
The thrown error contains a message with information about the uncalled routes.
58+
59+
### `called()`
60+
61+
The `called()` method checks if a specific route has been called. You can pass either a URL string (which defaults to a GET request) or a request pattern object:
62+
63+
```js {17,26, 28-34}
64+
import { MockServer, FetchMocker } from "mentoss";
65+
66+
const server1 = new MockServer("https://api1.example.com");
67+
const server2 = new MockServer("https://api2.example.com");
68+
const mocker = new FetchMocker({ servers: [server1, server2] });
69+
70+
server1.post(
71+
{
72+
url: "/users",
73+
body: {
74+
name: "John Doe",
75+
},
76+
},
77+
200,
78+
);
79+
80+
console.log(mocker.called("https://api1.example.com/users")); // false
81+
82+
await mocker.fetch("https://api1.example.com/users", {
83+
method: "POST",
84+
body: {
85+
name: "John Doe",
86+
},
87+
});
88+
89+
console.log(mocker.called("https://api1.example.com/users")); // true
90+
91+
console.log(mocker.called({
92+
url: "https://api1.example.com/users",
93+
method: "POST",
94+
body: {
95+
name: "John Doe",
96+
},
97+
})); // true
98+
```
99+
100+
In this example, the `called()` method returns `false` because the `/users` route has not been called yet. The matching is done using the same comparison logic as if a `fetch()` request was made to the route.
101+
102+
### `uncalledRoutes`
103+
104+
The `uncalledRoutes` property returns an array with information about uncalled routes. You can use this property to check if any routes have not been called:
105+
106+
```js {12}
107+
import { MockServer, FetchMocker } from "mentoss";
108+
109+
const server1 = new MockServer("https://api1.example.com");
110+
const server2 = new MockServer("https://api2.example.com");
111+
const mocker = new FetchMocker({ servers: [server1, server2] });
112+
113+
server1.get("/users", 200);
114+
server2.post("/users", 200);
115+
116+
await mocker.fetch("https://api1.example.com/users");
117+
118+
console.log(mocker.uncalledRoutes); // ["🚧 [Route: POST https://api2.example.com/users -> 200]"]
119+
```
120+
121+
The `uncalledRoutes` property is helpful when you want to format your own error messages or assertions.
122+
123+
<Aside type="tip">
124+
The `MockServer` class also supports `allRoutesCalled()`, `assertAllRoutesCalled()`, and `called()` methods, as well as the `uncalledRoutes` property. These methods and property work similarly to the `FetchMocker` equivalents except they only check routes on the given server.
125+
</Aside>
126+
127+
## Additional Helpers
128+
129+
### `clearAll()`
130+
131+
The `clearAll()` method removes all routes from all servers so you can reuse the same server for multiple tests.
132+
133+
```js {10}
134+
import { MockServer, FetchMocker } from "mentoss";
135+
136+
const server1 = new MockServer("https://api1.example.com");
137+
const server2 = new MockServer("https://api2.example.com");
138+
const mocker = new FetchMocker({ servers: [server1, server2] });
139+
140+
server1.get("/users", 200);
141+
server2.post("/users", 200);
142+
143+
mocker.clearAll();
144+
145+
await mocker.fetch("https://api1.example.com/users"); // Error!
146+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
title: "Testing Helpers: Mock Server"
3+
description: The mock server methods that are helpful for testing.
4+
---
5+
6+
import { Aside } from "@astrojs/starlight/components";
7+
8+
The mock server provides several methods to help verify that your requests were made as expected.
9+
10+
## Verifying Route Calls
11+
12+
Tracking which routes have been called is a key part of many tests. The mock server provides several methods and properties to help with this.
13+
14+
### `allRoutesCalled()`
15+
16+
The `allRoutesCalled()` method checks if all routes have been called. It returns `true` if all routes have been called at least once, and `false` otherwise:
17+
18+
```js {9, 12, 15}
19+
import { MockServer, FetchMocker } from "mentoss";
20+
21+
const server = new MockServer("https://api.example.com");
22+
const mocker = new FetchMocker({ servers: [server] });
23+
24+
server.get("/users", 200);
25+
server.post("/users", 200);
26+
27+
console.log(server.allRoutesCalled()); // false
28+
29+
await mocker.fetch("https://api.example.com/users");
30+
console.log(server.allRoutesCalled()); // false
31+
32+
await mocker.fetch("https://api.example.com/users", { method: "POST" });
33+
console.log(server.allRoutesCalled()); // true
34+
```
35+
36+
In this example, the `allRoutesCalled()` method first returns `false` because neither the GET nor the POST `/users` route has been called yet. After both routes have been called, the method returns `true`.
37+
38+
### `assertAllRoutesCalled()`
39+
40+
The `assertAllRoutesCalled()` method is an assertion that checks if all routes have been called. It throws an error if any routes have not been called:
41+
42+
```js {10}
43+
import { MockServer, FetchMocker } from "mentoss";
44+
45+
const server = new MockServer("https://api.example.com");
46+
const mocker = new FetchMocker({ servers: [server] });
47+
48+
server.get("/users", 200);
49+
server.post("/users", 200);
50+
51+
await mocker.fetch("https://api.example.com/users");
52+
server.assertAllRoutesCalled(); // Error!
53+
```
54+
55+
The thrown error contains a message with information about the uncalled routes.
56+
57+
### `called()`
58+
59+
The `called()` method checks if a specific route has been called. You can pass either a URL string (which defaults to a GET request) or a request pattern object:
60+
61+
```js {16,25, 27-33}
62+
import { MockServer, FetchMocker } from "mentoss";
63+
64+
const server = new MockServer("https://api.example.com");
65+
const mocker = new FetchMocker({ servers: [server] });
66+
67+
server.post(
68+
{
69+
url: "/users",
70+
body: {
71+
name: "John Doe",
72+
},
73+
},
74+
200,
75+
);
76+
77+
console.log(server.called("/users")); // false
78+
79+
await mocker.fetch("https://api.example.com/users", {
80+
method: "POST",
81+
body: {
82+
name: "John Doe",
83+
},
84+
});
85+
86+
console.log(server.called("/users")); // true
87+
88+
console.log(server.called({
89+
url: "/users",
90+
method: "POST",
91+
body: {
92+
name: "John Doe",
93+
},
94+
})); // true
95+
```
96+
97+
In this example, the `called()` method returns `false` because the `/users` route has not been called yet. The matching is done using the same comparison logic as if a `fetch()` request was made to the route.
98+
99+
### `uncalledRoutes`
100+
101+
The `uncalledRoutes` property returns an array with information about uncalled routes. You can use this property to check if any routes have not been called:
102+
103+
```js {11}
104+
import { MockServer, FetchMocker } from "mentoss";
105+
106+
const server = new MockServer("https://api.example.com");
107+
const mocker = new FetchMocker({ servers: [server] });
108+
109+
server.get("/users", 200);
110+
server.post("/users", 200);
111+
112+
await mocker.fetch("https://api.example.com/users");
113+
114+
console.log(server.uncalledRoutes); // ["🚧 [Route: POST https://api.example.com/users -> 200]"]
115+
```
116+
117+
The `uncalledRoutes` property is helpful when you want to format your own error messages or assertions.
118+
119+
<Aside type="tip">
120+
The `FetchMocker` class also supports `allRoutesCalled()`, `assertAllRoutesCalled()`, and `called()` methods, as well as the `uncalledRoutes` property. These methods and property work similarly to the `MockServer` methods, but they check if the routes have been called on all servers.
121+
</Aside>
122+
123+
## Additional Helpers
124+
125+
### `clear()`
126+
127+
The `clear()` method removes all routes from the server so you can reuse the same server for multiple tests.
128+
129+
```js {8}
130+
import { MockServer, FetchMocker } from "mentoss";
131+
132+
const server = new MockServer("https://api.example.com");
133+
const mocker = new FetchMocker({ servers: [server] });
134+
135+
server.get("/users", 200);
136+
137+
server.clear();
138+
139+
await mocker.fetch("https://api.example.com/users"); // Error!
140+
```
141+
142+
<Aside type="note">
143+
The `FetchMocker#clearAll()` method calls `clear()` on each registered server.
144+
</Aside>

src/fetch-mocker.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Partial matches:
5050
${
5151
traces
5252
.map(trace => {
53-
let traceMessage = `🚧 ${trace.route.toString()}:`;
53+
let traceMessage = `${trace.title}:`;
5454
5555
trace.messages.forEach(message => {
5656
traceMessage += `\n ${message}`;
@@ -337,6 +337,8 @@ export class FetchMocker {
337337
return preflightData;
338338
}
339339

340+
// #region: Testing Helpers
341+
340342
/**
341343
* Determines if a request was made.
342344
* @param {string|RequestPattern} request The request to match.
@@ -358,7 +360,34 @@ export class FetchMocker {
358360
allRoutesCalled() {
359361
return this.#servers.every(server => server.allRoutesCalled());
360362
}
363+
364+
/**
365+
* Gets the uncalled routes.
366+
* @return {string[]} The uncalled routes.
367+
*/
368+
get uncalledRoutes() {
369+
return this.#servers.flatMap(server => server.uncalledRoutes);
370+
}
371+
372+
/**
373+
* Asserts that all routes were called.
374+
* @returns {void}
375+
* @throws {Error} When not all routes were called.
376+
*/
377+
assertAllRoutesCalled() {
378+
const uncalledRoutes = this.uncalledRoutes;
379+
380+
if (uncalledRoutes.length > 0) {
381+
throw new Error(
382+
`Not all routes were called. Uncalled routes:\n${uncalledRoutes.join(
383+
"\n",
384+
)}`,
385+
);
386+
}
387+
}
361388

389+
// #endregion: Testing Helpers
390+
362391
/**
363392
* Unmocks the global fetch function.
364393
* @returns {void}

0 commit comments

Comments
 (0)