Skip to content

Commit 1565e5d

Browse files
committed
feat(response): composable response building
1 parent c559d78 commit 1565e5d

File tree

4 files changed

+134
-14
lines changed

4 files changed

+134
-14
lines changed

src/response-utils.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,79 @@
1-
import { HandlerArgument, MockHandler, MockHandlerFunction } from './types';
1+
import {
2+
HandlerArgument,
3+
MockHandler,
4+
MockHandlerFunction,
5+
ResponseData
6+
} from './types';
27

38
function execHandler(
49
handler: MockHandler,
510
args: HandlerArgument
6-
): Promise<Response> {
11+
): Promise<ResponseData> {
712
if (typeof handler === 'function') {
813
return handler(args);
914
} else {
1015
return ResponseUtils.jsonPromise(handler);
1116
}
1217
}
1318

19+
function unwrap(
20+
args: HandlerArgument
21+
): (handler: MockHandler) => Promise<ResponseData> {
22+
return (handler: MockHandler) => {
23+
if (typeof handler === 'function') {
24+
return handler(args);
25+
} else {
26+
return ResponseUtils.jsonPromise(handler);
27+
}
28+
};
29+
}
30+
31+
function merge(into: ResponseData, data: ResponseData): ResponseData {
32+
return {
33+
body: into.body || data.body,
34+
status: into.status || data.status,
35+
statusText: into.statusText || data.statusText,
36+
headers: Object.assign({}, data.headers, into.headers)
37+
};
38+
}
39+
1440
export default class ResponseUtils {
1541
static json(json: object): MockHandlerFunction {
1642
return () => ResponseUtils.jsonPromise(json);
1743
}
1844

19-
static jsonPromise(json: MockHandler): Promise<Response> {
20-
const response: Response = new Response(JSON.stringify(json));
45+
static jsonPromise(json: MockHandler): Promise<ResponseData> {
46+
const response: ResponseData = { body: JSON.stringify(json) };
2147
return Promise.resolve(response);
2248
}
2349

2450
static delayed(delay: number, handler: MockHandler): MockHandler {
2551
return (args: HandlerArgument) => {
26-
return new Promise<Response>(resolve => {
52+
return new Promise<ResponseData>(resolve => {
2753
setTimeout(() => resolve(execHandler(handler, args)), delay);
2854
});
2955
};
3056
}
57+
58+
static statusCode(status: number): MockHandler {
59+
return (args: HandlerArgument) => {
60+
const response: ResponseData = { status };
61+
return Promise.resolve(response);
62+
};
63+
}
64+
65+
static statusText(statusText: string): MockHandler {
66+
return (args: HandlerArgument) => {
67+
const response: ResponseData = { statusText };
68+
return Promise.resolve(response);
69+
};
70+
}
71+
72+
static combine(...handlers: MockHandler[]): MockHandler {
73+
return (args: HandlerArgument) => {
74+
return Promise.all(
75+
handlers.map(unwrap(args))
76+
).then((data: ResponseData[]) => data.reduce(merge, {}));
77+
};
78+
}
3179
}

src/types.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,19 @@ export interface RouteMatcher {
2525
matcherUrl?: MatcherUrl;
2626
}
2727

28+
export interface ResponseData {
29+
body?: any;
30+
headers?: HeadersInit;
31+
status?: number;
32+
statusText?: string;
33+
}
34+
2835
export type MockHandler =
29-
| ((args: HandlerArgument) => Promise<Response>)
36+
| ((args: HandlerArgument) => Promise<ResponseData>)
3037
| object;
31-
export type MockHandlerFunction = (args: HandlerArgument) => Promise<Response>;
38+
export type MockHandlerFunction = (
39+
args: HandlerArgument
40+
) => Promise<ResponseData>;
3241
export type RequestUrl = Opaque<'RequestUrl', string>;
3342
export type MatcherUrl = Opaque<'MatcherUrl', string>;
3443

@@ -38,5 +47,8 @@ export interface Route {
3847
}
3948
export interface Configuration {
4049
enableFallback: boolean;
41-
middleware: (request: HandlerArgument, response: Response) => Response;
50+
middleware: (
51+
request: HandlerArgument,
52+
response: ResponseData
53+
) => ResponseData;
4254
}

src/yet-another-fetch-mock.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
MatcherUrl,
55
MockHandler,
66
RequestUrl,
7+
ResponseData,
78
Route,
89
RouteMatcher
910
} from './types';
@@ -86,7 +87,7 @@ class FetchMock {
8687
const queryParams = findQueryParams(url);
8788
const body = findBody(input, init);
8889
let pathParams: object = {};
89-
let response: Promise<Response>;
90+
let response: Promise<ResponseData>;
9091

9192
if (typeof matchingRoute === 'undefined') {
9293
if (this.configuration.enableFallback) {
@@ -107,12 +108,21 @@ class FetchMock {
107108
});
108109
}
109110

110-
return response.then(resp =>
111-
this.configuration.middleware(
112-
{ input, init, url, method, queryParams, pathParams, body },
113-
resp
111+
return response
112+
.then(resp =>
113+
this.configuration.middleware(
114+
{ input, init, url, method, queryParams, pathParams, body },
115+
resp
116+
)
114117
)
115-
);
118+
.then(
119+
(data: ResponseData) =>
120+
new Response(data.body, {
121+
status: data.status,
122+
statusText: data.statusText,
123+
headers: data.headers
124+
})
125+
);
116126
}
117127

118128
private findMatchingRoute(

test/yet-another-fetch-mock.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,54 @@ describe('FetchMock', () => {
169169
done();
170170
});
171171
});
172+
173+
it('should support responding with status codes', done => {
174+
mock.get('/error', ResponseUtils.statusCode(404));
175+
176+
fetch('/error').then(resp => {
177+
expect(resp.ok).toBe(false);
178+
expect(resp.status).toBe(404);
179+
done();
180+
});
181+
});
182+
183+
it('should be able to combine response utils', done => {
184+
mock.get(
185+
'/combine',
186+
ResponseUtils.combine(
187+
ResponseUtils.json({ key: 'value' }),
188+
ResponseUtils.statusCode(201)
189+
)
190+
);
191+
192+
mock.get(
193+
'/combine2',
194+
ResponseUtils.combine(
195+
ResponseUtils.statusCode(202),
196+
{ key: 'value2' },
197+
ResponseUtils.statusText('Its ok')
198+
)
199+
);
200+
201+
const first = fetch('/combine')
202+
.then(resp => {
203+
expect(resp.status).toBe(201);
204+
return resp.json();
205+
})
206+
.then(json => {
207+
expect(json.key).toBe('value');
208+
});
209+
210+
const second = fetch('/combine2')
211+
.then(resp => {
212+
expect(resp.status).toBe(202);
213+
expect(resp.statusText).toBe('Its ok');
214+
return resp.json();
215+
})
216+
.then(json => {
217+
expect(json.key).toBe('value2');
218+
});
219+
220+
Promise.all([first, second]).then(() => done());
221+
});
172222
});

0 commit comments

Comments
 (0)