Skip to content

Commit 50097dc

Browse files
committed
feat(configuration): Added configuration, and a few helper utils
BREAKING CHANGE: init renamed to configure
1 parent 9553c41 commit 50097dc

File tree

4 files changed

+89
-26
lines changed

4 files changed

+89
-26
lines changed

src/response-utils.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
1-
import { FetchMethod, MockHandlerFunction } from './types';
1+
import { HandlerArgument, MockHandler, MockHandlerFunction } from './types';
2+
3+
function execHandler(
4+
handler: MockHandler,
5+
args: HandlerArgument
6+
): Promise<Response> {
7+
if (typeof handler === 'function') {
8+
return handler(args);
9+
} else {
10+
return ResponseUtils.jsonPromise(handler);
11+
}
12+
}
213

314
export default class ResponseUtils {
415
static json(json: object): MockHandlerFunction {
516
return () => ResponseUtils.jsonPromise(json);
617
}
718

8-
static jsonPromise(json: object): Promise<Response> {
19+
static jsonPromise(json: MockHandler): Promise<Response> {
920
const response: Response = new Response(JSON.stringify(json));
1021
return Promise.resolve(response);
1122
}
1223

13-
static use(fn: FetchMethod): MockHandlerFunction {
14-
return ({ input, init }) => fn(input, init);
24+
static delayed(delay: number, handler: MockHandler): MockHandler {
25+
return (args: HandlerArgument) => {
26+
return new Promise<Response>(resolve => {
27+
setTimeout(() => resolve(execHandler(handler, args)), delay);
28+
});
29+
};
1530
}
1631
}

src/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ export interface HandlerArgument {
2020
method: HttpMethod;
2121
}
2222

23-
export type FetchMethod = (
24-
input: RequestInfo,
25-
init?: RequestInit
26-
) => Promise<Response>;
2723
export interface RouteMatcher {
2824
test: (input: RequestInfo, init?: RequestInit) => boolean;
2925
matcherUrl?: MatcherUrl;
@@ -40,3 +36,7 @@ export interface Route {
4036
matcher: RouteMatcher;
4137
handler: MockHandlerFunction;
4238
}
39+
export interface Configuration {
40+
enableFallback: boolean;
41+
middleware: (request: HandlerArgument, response: Response) => Response;
42+
}

src/yet-another-fetch-mock.ts

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import {
2-
FetchMethod,
2+
Configuration,
33
HttpMethod,
44
MatcherUrl,
55
MockHandler,
6-
MockHandlerFunction,
76
RequestUrl,
87
Route,
98
RouteMatcher
@@ -18,20 +17,32 @@ import {
1817
import MatcherUtils from './matcher-utils';
1918
import ResponseUtils from './response-utils';
2019

20+
const defaultConfiguration: Configuration = {
21+
enableFallback: true,
22+
middleware: (request, response) => response
23+
};
24+
2125
class FetchMock {
22-
public realFetch: FetchMethod;
26+
private realFetch: (
27+
input: RequestInfo,
28+
init?: RequestInit
29+
) => Promise<Response>;
30+
private configuration: Configuration;
2331
private routes: Route[];
2432
private scope: GlobalFetch;
2533

26-
constructor(scope: GlobalFetch) {
34+
constructor(scope: GlobalFetch, configuration: Partial<Configuration>) {
2735
this.scope = scope;
36+
this.configuration = Object.assign({}, defaultConfiguration, configuration);
2837
this.realFetch = scope.fetch;
2938
this.routes = [];
3039
this.scope.fetch = this.fetchproxy.bind(this);
3140
}
3241

33-
static init(): FetchMock {
34-
return new FetchMock(window);
42+
static configure(
43+
configuration: Partial<Configuration> = defaultConfiguration
44+
): FetchMock {
45+
return new FetchMock(window, configuration);
3546
}
3647

3748
restore() {
@@ -55,9 +66,7 @@ class FetchMock {
5566
}
5667

5768
mock(matcher: RouteMatcher, handler: MockHandler) {
58-
if (handler === this.realFetch) {
59-
this.routes.push({ matcher, handler: ResponseUtils.use(this.realFetch) });
60-
} else if (typeof handler === 'function') {
69+
if (typeof handler === 'function') {
6170
this.routes.push({ matcher, handler });
6271
} else {
6372
this.routes.push({ matcher, handler: ResponseUtils.json(handler) });
@@ -72,18 +81,38 @@ class FetchMock {
7281
input,
7382
init
7483
);
75-
if (typeof matchingRoute === 'undefined') {
76-
throw new Error('Matching route not found...');
77-
}
78-
79-
const handler: MockHandlerFunction = matchingRoute.handler;
8084
const url: RequestUrl = findRequestUrl(input, init);
8185
const method: HttpMethod = findRequestMethod(input, init);
82-
const pathParams = findPathParams(url, matchingRoute.matcher.matcherUrl);
8386
const queryParams = findQueryParams(url);
8487
const body = findBody(input, init);
88+
let pathParams: object = {};
89+
let response: Promise<Response>;
8590

86-
return handler({ input, init, url, method, pathParams, queryParams, body });
91+
if (typeof matchingRoute === 'undefined') {
92+
if (this.configuration.enableFallback) {
93+
response = this.realFetch(input, init);
94+
} else {
95+
throw new Error(`Did not find any matching route for url: ${url}`);
96+
}
97+
} else {
98+
pathParams = findPathParams(url, matchingRoute.matcher.matcherUrl);
99+
response = matchingRoute.handler({
100+
input,
101+
init,
102+
url,
103+
method,
104+
pathParams,
105+
queryParams,
106+
body
107+
});
108+
}
109+
110+
return response.then(resp =>
111+
this.configuration.middleware(
112+
{ input, init, url, method, queryParams, pathParams, body },
113+
resp
114+
)
115+
);
87116
}
88117

89118
private findMatchingRoute(

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('utils', () => {
2222
describe('FetchMock', () => {
2323
let mock: FetchMock;
2424
beforeEach(() => {
25-
mock = FetchMock.init();
25+
mock = FetchMock.configure();
2626
});
2727

2828
afterEach(() => {
@@ -128,6 +128,9 @@ describe('FetchMock', () => {
128128
});
129129

130130
it('should throw error if no route matches', () => {
131+
mock.restore();
132+
FetchMock.configure({ enableFallback: false });
133+
131134
expect(() => {
132135
fetchToJson('/test');
133136
}).toThrow();
@@ -139,7 +142,6 @@ describe('FetchMock', () => {
139142

140143
it('should support fallback to realFetch', done => {
141144
mock.get('/testurl', { key: 'testurl' });
142-
mock.get('*', mock.realFetch);
143145

144146
const mocked = fetchToJson('/testurl').then(json =>
145147
expect(json.key).toBe('testurl')
@@ -150,4 +152,21 @@ describe('FetchMock', () => {
150152

151153
Promise.all([mocked, fallback]).then(() => done());
152154
});
155+
156+
it('should support delayed responses', done => {
157+
mock.get('/test', ResponseUtils.delayed(200, { key: 'delayed' }));
158+
mock.get(
159+
'/test2',
160+
ResponseUtils.delayed(200, ResponseUtils.json({ key: 'delayed2' }))
161+
);
162+
const startTime = new Date().getTime();
163+
164+
Promise.all([fetchToJson('/test'), fetchToJson('/test2')]).then(json => {
165+
const endTime = new Date().getTime();
166+
expect(json[0].key).toBe('delayed');
167+
expect(json[1].key).toBe('delayed2');
168+
expect(endTime - startTime).toBeGreaterThan(200);
169+
done();
170+
});
171+
});
153172
});

0 commit comments

Comments
 (0)