Skip to content

Commit bc387f0

Browse files
committed
encourage type safety in mock overrides
1 parent 2e7dbf2 commit bc387f0

File tree

3 files changed

+61
-62
lines changed

3 files changed

+61
-62
lines changed

docs/mocks.md

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,19 @@ import { mockedGetRegistryV01Servers } from "@mocks/fixtures/registry_v0_1_serve
5353

5454
describe("ServerList", () => {
5555
it("shows empty state when no servers", async () => {
56-
// Override to return empty list for this test only
57-
mockedGetRegistryV01Servers.override(() =>
58-
HttpResponse.json({ servers: [], metadata: { count: 0 } })
59-
);
56+
// Override to return empty list for this test only (type-safe)
57+
mockedGetRegistryV01Servers.override(() => ({
58+
servers: [],
59+
metadata: { count: 0 },
60+
}));
6061

6162
render(<ServerList />);
6263
expect(screen.getByText("No servers available")).toBeVisible();
6364
});
6465

6566
it("shows error state on API failure", async () => {
66-
// Override to return an error
67-
mockedGetRegistryV01Servers.override(() =>
67+
// Use overrideHandler for error responses
68+
mockedGetRegistryV01Servers.overrideHandler(() =>
6869
HttpResponse.json({ error: "Server error" }, { status: 500 })
6970
);
7071

@@ -87,11 +88,11 @@ interface AutoAPIMockInstance<T> {
8788
// The default fixture data
8889
defaultValue: T;
8990

90-
// Override the response for the current test (full control)
91-
override(fn: (data: T, info: ResponseResolverInfo) => Response): this;
91+
// Override the response data (type-safe, preferred)
92+
override(fn: (data: T, info: ResponseResolverInfo) => T): this;
9293

93-
// Override just the response data (simpler API)
94-
overrideResponse(fn: (data: T, info: ResponseResolverInfo) => T): this;
94+
// Override the full handler (for errors, network failures, invalid data)
95+
overrideHandler(fn: (data: T, info: ResponseResolverInfo) => Response): this;
9596

9697
// Reset to default behavior (called automatically before each test)
9798
reset(): this;
@@ -101,41 +102,41 @@ interface AutoAPIMockInstance<T> {
101102
}
102103
```
103104

104-
### Choosing Between `override` and `overrideResponse`
105+
### Choosing Between `override` and `overrideHandler`
105106

106-
Use **`overrideResponse`** (simpler) when you just want to change the response data:
107+
Use **`override`** (type-safe, preferred) when you just want to change the response data:
107108

108109
```typescript
109110
// Simple - just return the data you want
110-
mockedGetRegistryV01Servers.overrideResponse(() => ({
111+
mockedGetRegistryV01Servers.override(() => ({
111112
servers: [],
112113
metadata: { count: 0 },
113114
}));
114115

115116
// With default data modifications
116-
mockedGetRegistryV01Servers.overrideResponse((data) => ({
117+
mockedGetRegistryV01Servers.override((data) => ({
117118
...data,
118119
servers: data.servers?.slice(0, 3),
119120
}));
120121
```
121122

122-
Use **`override`** (full control) when you need:
123+
Use **`overrideHandler`** (full control) when you need:
123124
- Custom HTTP status codes (errors)
124125
- Non-JSON responses
125126
- Network errors
126127
- Invalid/malformed data for edge case testing
127128

128129
```typescript
129130
// Return error status
130-
mockedGetRegistryV01Servers.override(() =>
131+
mockedGetRegistryV01Servers.overrideHandler(() =>
131132
HttpResponse.json({ error: "Server error" }, { status: 500 })
132133
);
133134

134135
// Network error
135-
mockedGetRegistryV01Servers.override(() => HttpResponse.error());
136+
mockedGetRegistryV01Servers.overrideHandler(() => HttpResponse.error());
136137

137138
// Testing invalid data shapes
138-
mockedGetRegistryV01Servers.override(() =>
139+
mockedGetRegistryV01Servers.overrideHandler(() =>
139140
HttpResponse.json({ servers: [{ server: null }] })
140141
);
141142
```
@@ -149,14 +150,14 @@ Overrides are automatically reset before each test via `beforeEach()` in `src/mo
149150
Access the default fixture data to make partial modifications:
150151

151152
```typescript
152-
// With overrideResponse (cleaner)
153-
mockedGetRegistryV01Servers.overrideResponse((data) => ({
153+
// With override (type-safe, preferred)
154+
mockedGetRegistryV01Servers.override((data) => ({
154155
...data,
155156
servers: data.servers?.slice(0, 1),
156157
}));
157158

158-
// With override (when you need the Response wrapper)
159-
mockedGetRegistryV01Servers.override((data) =>
159+
// With overrideHandler (when you need the Response wrapper)
160+
mockedGetRegistryV01Servers.overrideHandler((data) =>
160161
HttpResponse.json({
161162
...data,
162163
servers: data.servers?.slice(0, 1),
@@ -169,16 +170,16 @@ mockedGetRegistryV01Servers.override((data) =>
169170
Both methods receive request info as the second parameter:
170171

171172
```typescript
172-
// With overrideResponse
173-
mockedGetRegistryV01Servers.overrideResponse((data, info) => {
173+
// With override (type-safe)
174+
mockedGetRegistryV01Servers.override((data, info) => {
174175
const cursor = new URL(info.request.url).searchParams.get("cursor");
175176
return cursor === "page2"
176177
? { servers: [], metadata: { count: 0 } }
177178
: data;
178179
});
179180

180-
// With override (when you need different status codes based on request)
181-
mockedGetRegistryV01Servers.override((data, info) => {
181+
// With overrideHandler (when you need different status codes based on request)
182+
mockedGetRegistryV01Servers.overrideHandler((data, info) => {
182183
const authHeader = info.request.headers.get("Authorization");
183184
if (!authHeader) {
184185
return HttpResponse.json({ error: "Unauthorized" }, { status: 401 });

src/app/catalog/actions.test.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ describe("getServers", () => {
2828
expect(servers[0].title).toBe("AWS Nova Canvas");
2929
});
3030

31-
// Using overrideResponse - simpler API when you just want to change the data
3231
it("returns empty array when API returns no servers", async () => {
33-
mockedGetRegistryV01Servers.overrideResponse(() => ({
32+
mockedGetRegistryV01Servers.override(() => ({
3433
servers: [],
3534
metadata: { count: 0 },
3635
}));
@@ -40,9 +39,11 @@ describe("getServers", () => {
4039
expect(servers).toEqual([]);
4140
});
4241

43-
// Using override - needed when returning non-JSON responses like null
42+
// Using overrideHandler - needed when returning non-JSON responses like null
4443
it("returns empty array when API returns null data", async () => {
45-
mockedGetRegistryV01Servers.override(() => HttpResponse.json(null));
44+
mockedGetRegistryV01Servers.overrideHandler(() =>
45+
HttpResponse.json(null),
46+
);
4647

4748
const servers = await getServers();
4849

@@ -51,34 +52,34 @@ describe("getServers", () => {
5152
});
5253

5354
describe("error handling", () => {
54-
// Using override - needed for error status codes
55+
// Using overrideHandler - needed for error status codes
5556
it("throws on 500 server error", async () => {
56-
mockedGetRegistryV01Servers.override(() =>
57+
mockedGetRegistryV01Servers.overrideHandler(() =>
5758
HttpResponse.json({ error: "Internal Server Error" }, { status: 500 }),
5859
);
5960

6061
await expect(getServers()).rejects.toBeDefined();
6162
});
6263

6364
it("throws on 401 unauthorized", async () => {
64-
mockedGetRegistryV01Servers.override(() =>
65+
mockedGetRegistryV01Servers.overrideHandler(() =>
6566
HttpResponse.json({ error: "Unauthorized" }, { status: 401 }),
6667
);
6768

6869
await expect(getServers()).rejects.toBeDefined();
6970
});
7071

7172
it("throws on network error", async () => {
72-
mockedGetRegistryV01Servers.override(() => HttpResponse.error());
73+
mockedGetRegistryV01Servers.overrideHandler(() => HttpResponse.error());
7374

7475
await expect(getServers()).rejects.toBeDefined();
7576
});
7677
});
7778

7879
describe("data transformation", () => {
79-
// Using override - needed when testing invalid data shapes (null servers)
80+
// Using overrideHandler - needed when testing invalid data shapes (null servers)
8081
it("filters out null servers from response", async () => {
81-
mockedGetRegistryV01Servers.override((data) =>
82+
mockedGetRegistryV01Servers.overrideHandler((data) =>
8283
HttpResponse.json({
8384
...data,
8485
servers: [
@@ -99,9 +100,9 @@ describe("getServers", () => {
99100
]);
100101
});
101102

102-
// Using override - needed when testing invalid data shapes (undefined servers)
103+
// Using overrideHandler - needed when testing invalid data shapes (undefined servers)
103104
it("filters out undefined servers from response", async () => {
104-
mockedGetRegistryV01Servers.override(() =>
105+
mockedGetRegistryV01Servers.overrideHandler(() =>
105106
HttpResponse.json({
106107
servers: [
107108
{ server: { name: "valid/server", title: "Valid" } },
@@ -118,9 +119,8 @@ describe("getServers", () => {
118119
expect(servers[0].name).toBe("valid/server");
119120
});
120121

121-
// Using overrideResponse - valid response structure
122122
it("extracts server objects from nested response structure", async () => {
123-
mockedGetRegistryV01Servers.overrideResponse(() => ({
123+
mockedGetRegistryV01Servers.override(() => ({
124124
servers: [
125125
{
126126
server: {
@@ -147,9 +147,8 @@ describe("getServers", () => {
147147
});
148148

149149
describe("using default data in overrides", () => {
150-
// Using overrideResponse - cleaner syntax for data modifications
151150
it("can modify specific server titles", async () => {
152-
mockedGetRegistryV01Servers.overrideResponse((data) => ({
151+
mockedGetRegistryV01Servers.override((data) => ({
153152
...data,
154153
servers: data.servers?.map((item) => ({
155154
...item,
@@ -166,7 +165,7 @@ describe("getServers", () => {
166165
});
167166

168167
it("can limit the number of returned servers", async () => {
169-
mockedGetRegistryV01Servers.overrideResponse((data) => ({
168+
mockedGetRegistryV01Servers.override((data) => ({
170169
...data,
171170
servers: data.servers?.slice(0, 3),
172171
metadata: { count: 3 },
@@ -178,7 +177,7 @@ describe("getServers", () => {
178177
});
179178

180179
it("can filter servers by criteria", async () => {
181-
mockedGetRegistryV01Servers.overrideResponse((data) => ({
180+
mockedGetRegistryV01Servers.override((data) => ({
182181
...data,
183182
servers: data.servers?.filter((item) =>
184183
item.server?.name?.includes("google"),
@@ -192,11 +191,10 @@ describe("getServers", () => {
192191
});
193192

194193
describe("request-aware overrides", () => {
195-
// Using overrideResponse with request info
196-
it("can access request info in overrideResponse", async () => {
194+
it("can access request info in override", async () => {
197195
let capturedUrl: string | undefined;
198196

199-
mockedGetRegistryV01Servers.overrideResponse((data, info) => {
197+
mockedGetRegistryV01Servers.override((data, info) => {
200198
capturedUrl = info.request.url;
201199
return data;
202200
});
@@ -206,9 +204,9 @@ describe("getServers", () => {
206204
expect(capturedUrl).toContain("/registry/v0.1/servers");
207205
});
208206

209-
// Using override - needed when response depends on request and may return different status codes
207+
// Using overrideHandler - needed when response depends on request and may return different status codes
210208
it("can vary response based on request headers", async () => {
211-
mockedGetRegistryV01Servers.override((data, info) => {
209+
mockedGetRegistryV01Servers.overrideHandler((data, info) => {
212210
const authHeader = info.request.headers.get("Authorization");
213211
if (authHeader?.includes("mock-token")) {
214212
return HttpResponse.json(data);

src/mocks/autoAPIMock.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { HttpResponse } from "msw";
33

44
type ResponseResolverInfo = Parameters<HttpResponseResolver>[0];
55

6-
type OverrideFn<T> = (data: T, info: ResponseResolverInfo) => Response;
7-
type OverrideResponseFn<T> = (data: T, info: ResponseResolverInfo) => T;
6+
type OverrideHandlerFn<T> = (data: T, info: ResponseResolverInfo) => Response;
7+
type OverrideFn<T> = (data: T, info: ResponseResolverInfo) => T;
88

99
export interface AutoAPIMockInstance<T> {
1010
generatedHandler: HttpResponseResolver;
1111
override: (fn: OverrideFn<T>) => AutoAPIMockInstance<T>;
12-
overrideResponse: (fn: OverrideResponseFn<T>) => AutoAPIMockInstance<T>;
12+
overrideHandler: (fn: OverrideHandlerFn<T>) => AutoAPIMockInstance<T>;
1313
reset: () => AutoAPIMockInstance<T>;
1414
defaultValue: T;
1515
}
@@ -18,31 +18,31 @@ export interface AutoAPIMockInstance<T> {
1818
const registry: Set<AutoAPIMockInstance<unknown>> = new Set();
1919

2020
export function AutoAPIMock<T>(defaultValue: T): AutoAPIMockInstance<T> {
21-
let overrideFn: OverrideFn<T> | null = null;
21+
let overrideHandlerFn: OverrideHandlerFn<T> | null = null;
2222

2323
const instance: AutoAPIMockInstance<T> = {
2424
defaultValue,
2525

2626
generatedHandler(info: ResponseResolverInfo) {
27-
if (overrideFn) {
28-
return overrideFn(defaultValue, info);
27+
if (overrideHandlerFn) {
28+
return overrideHandlerFn(defaultValue, info);
2929
}
3030
return HttpResponse.json(defaultValue as JsonBodyType);
3131
},
3232

3333
override(fn: OverrideFn<T>) {
34-
overrideFn = fn;
35-
return instance;
36-
},
37-
38-
overrideResponse(fn: OverrideResponseFn<T>) {
39-
return instance.override((data, info) =>
34+
return instance.overrideHandler((data, info) =>
4035
HttpResponse.json(fn(data, info) as JsonBodyType),
4136
);
4237
},
4338

39+
overrideHandler(fn: OverrideHandlerFn<T>) {
40+
overrideHandlerFn = fn;
41+
return instance;
42+
},
43+
4444
reset() {
45-
overrideFn = null;
45+
overrideHandlerFn = null;
4646
return instance;
4747
},
4848
};

0 commit comments

Comments
 (0)