Skip to content

Commit f32a3da

Browse files
authored
Merge pull request #14 from nutgaard/api-rework
feat(api): reworked api to make it less error prone
2 parents 929cd28 + 91de8a1 commit f32a3da

16 files changed

+352
-294
lines changed

README.md

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ const delayedErrorMock = FetchMock.configure({
3737

3838
### Examples
3939
```typescript
40-
mock.get('/my-url', { key: 'value' }); // Returns the object as the json-response
41-
mock.post('/another-url', { key: 'result of posting' });
40+
mock.get('/my-url', (req, res, ctx) => res(ctx.json({ key: 'value' }))); // Returns the object as the json-response
41+
mock.post('/another-url', (req, res, ctx) => res(ctx.json({ key: 'result of posting' })));
4242

4343

4444
// Creating dynamic content based on url
45-
mock.put('/api/:id/app', (args: HandlerArgument) => {
46-
// `args` contains the original parameters to `fetch`,
45+
mock.put('/api/:id/app', (req, res, ctx) => {
46+
// `req` contains the original parameters to `fetch`,
4747
// and in addition: url, http-verb, path parameters and query parameters
48-
return ResponseUtils.jsonPromise({
49-
id: args.pathParams.id,
50-
content: 'Some random content'
51-
});
48+
// `res` is used for combining and build your response based on helpers from `ctx`
49+
return res(
50+
ctx.json({id: req.pathParams.id, content: 'Some random content'})
51+
);
5252
});
5353

5454

@@ -59,13 +59,18 @@ mock.mock(
5959
MatcherUtils.url('/custom-url'),
6060
// Your custom matcher here
6161
),
62-
ResponseUtils.delayed(1000, { data: 'lots of data' })
62+
(res, req, ctx) => res(
63+
ctx.delay(1000),
64+
ctx.json({ data: 'lots of data' })
65+
)
6366
);
6467

6568
// Combining resultsUtils
66-
mock.get('/test/:id', ResponseUtils.delayed(1000, (args: HandlerArgument) => {
67-
return ResponseUtils.jsonPromise({ requestId: args.pathParams.id });
68-
}));
69+
mock.get('/test/:id', (req, res, ctx) => res(
70+
ctx.delay(1000),
71+
ctx.header('X-My-Header' ,'HeaderValue'),
72+
ctx.json({ requestId: req.pathParams.id })
73+
));
6974
```
7075

7176
### Teardown
@@ -102,7 +107,7 @@ describe('test using yet-another-fetch-mock', () => {
102107
});
103108

104109
it('should capture calls', (done) => {
105-
mock.get('/test/:id', { data: 'test' });
110+
mock.get('/test/:id', (req, res, ctx) => res(ctx.json({ data: 'test' })));
106111
Promise.all([
107112
fetch('/test/121'),
108113
fetch('/test/122')
@@ -123,23 +128,16 @@ describe('test using yet-another-fetch-mock', () => {
123128
Full documentation of types can be seen [here](https://www.utgaard.xyz/yet-another-fetch-mock/),
124129
or [here](https://github.com/nutgaard/yet-another-fetch-mock/blob/master/src/types.ts) if you prefer reading typescript code.
125130

126-
##### !!!NB!!!
127-
128-
**Known issue:**
129-
`Argument of type '({ queryParams }: HandlerArgument) => MyDataInterface' is not assignable to parameter of type 'MockHandler'.`
130-
The solution is often to ensure that `MyDataInterface` is also a `JsonObject`.
131-
E.g
132-
```
133-
mock.get('/my-url', () => mockData as MyDataInterface & JsonObject)
134-
```
135-
136-
137-
138131
### Tips
139132

140-
* It is recommended to toggle the fetch-mock functionality behind an environment variable, as to allow uglify etc to remove the code for production build.
141-
* Take a look at the original [readme](https://github.com/alexjoverm/typescript-library-starter/blob/master/README.md);
133+
It is recommended to toggle the fetch-mock functionality behind an environment variable, as to allow uglify/envify (or similar) to remove the code for production builds.
142134

135+
As an example;
136+
```typescript jsx
137+
if (process.env.USE_MOCK_SETUP === 'true') {
138+
require('./mocks')
139+
}
140+
```
143141

144142
## Credits
145143

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "yet-another-fetch-mock",
3-
"version": "0.0.0-development",
3+
"version": "4.0.0-beta.2",
44
"description": "",
55
"keywords": [
66
"fetch",

src/internal-utils.ts

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
import * as queryString from 'query-string';
2-
import {
3-
HandlerArgument,
4-
HttpMethod,
5-
MatcherUrl,
6-
MockHandler,
7-
MockHandlerFunction,
8-
RequestUrl,
9-
ResponseData
10-
} from './types';
2+
import { HttpMethod, MatcherUrl, RequestUrl } from './types';
113
import { Key } from 'path-to-regexp';
12-
import ResponseUtils from './response-utils';
13-
import { testPromise } from './promise-utils';
144
const pathToRegex = require('path-to-regexp');
155

166
export function findRequestUrl(input: RequestInfo, init?: RequestInit): RequestUrl {
@@ -60,21 +50,3 @@ export function findBody(input: RequestInfo, init?: RequestInit) {
6050
return init.body;
6151
}
6252
}
63-
64-
export function toMockHandlerFunction(handler: MockHandler): MockHandlerFunction {
65-
if (typeof handler === 'function') {
66-
return (args: HandlerArgument) =>
67-
new Promise<ResponseData>((resolve, reject) => {
68-
const result = handler(args);
69-
const isPromise = testPromise(result);
70-
if (isPromise) {
71-
resolve(result as Promise<ResponseData>);
72-
} else {
73-
const response: ResponseData = { body: JSON.stringify(result) } as ResponseData;
74-
resolve(response);
75-
}
76-
});
77-
} else {
78-
return ResponseUtils.json(handler);
79-
}
80-
}

src/matcher-utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ mock.get('/path-without-queryparam', ({ queryParams }) => {
8181
return httpMethodHelper(matcherUrl, 'GET');
8282
}
8383

84+
static head(matcherUrl: MatcherUrl): RouteMatcher {
85+
return httpMethodHelper(matcherUrl, 'HEAD');
86+
}
87+
8488
static post(matcherUrl: MatcherUrl): RouteMatcher {
8589
return httpMethodHelper(matcherUrl, 'POST');
8690
}
@@ -92,4 +96,16 @@ mock.get('/path-without-queryparam', ({ queryParams }) => {
9296
static del(matcherUrl: MatcherUrl): RouteMatcher {
9397
return httpMethodHelper(matcherUrl, 'DELETE');
9498
}
99+
100+
static connect(matcherUrl: MatcherUrl): RouteMatcher {
101+
return httpMethodHelper(matcherUrl, 'CONNECT');
102+
}
103+
104+
static options(matcherUrl: MatcherUrl): RouteMatcher {
105+
return httpMethodHelper(matcherUrl, 'OPTIONS');
106+
}
107+
108+
static patch(matcherUrl: MatcherUrl): RouteMatcher {
109+
return httpMethodHelper(matcherUrl, 'PATCH');
110+
}
95111
}

src/middleware-utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HandlerArgument, Middleware, ResponseData } from './types';
1+
import { MockRequest, Middleware, ResponseData } from './types';
22

33
const defaultFailure: ResponseData = {
44
status: 500,
@@ -7,7 +7,7 @@ const defaultFailure: ResponseData = {
77

88
export default class MiddlewareUtils {
99
static combine(...middlewares: Middleware[]): Middleware {
10-
return (request: HandlerArgument, response: ResponseData) => {
10+
return (request: MockRequest, response: ResponseData) => {
1111
return middlewares.reduce(
1212
(currentResponse, middleware) => currentResponse.then(resp => middleware(request, resp)),
1313
Promise.resolve(response)
@@ -16,7 +16,7 @@ export default class MiddlewareUtils {
1616
}
1717

1818
static delayMiddleware(delayMs: number): Middleware {
19-
return (request: HandlerArgument, response: ResponseData) => {
19+
return (request: MockRequest, response: ResponseData) => {
2020
return new Promise<ResponseData>(resolve => {
2121
setTimeout(() => resolve(response), delayMs);
2222
});
@@ -27,7 +27,7 @@ export default class MiddlewareUtils {
2727
probabilityOfFailure: number,
2828
failure: ResponseData = defaultFailure
2929
): Middleware {
30-
return (request: HandlerArgument, response: ResponseData) => {
30+
return (request: MockRequest, response: ResponseData) => {
3131
return new Promise<ResponseData>(resolve => {
3232
const rnd = Math.random();
3333

src/mock-context.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { MockRequest, HandlerResponseElement, ResponseData } from './types';
2+
3+
async function delay(ms: number): Promise<any> {
4+
return new Promise<ResponseData>(resolve => {
5+
setTimeout(() => resolve(), ms);
6+
});
7+
}
8+
9+
class MockContext {
10+
private req: MockRequest;
11+
private realFetch: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
12+
13+
constructor(
14+
req: MockRequest,
15+
realFetch: (input: RequestInfo, init?: RequestInit) => Promise<Response>
16+
) {
17+
this.req = req;
18+
this.realFetch = realFetch;
19+
}
20+
21+
status(code: number): HandlerResponseElement {
22+
return async (data: ResponseData) => {
23+
data.status = code;
24+
return data;
25+
};
26+
}
27+
28+
statusText(message: string): HandlerResponseElement {
29+
return async (data: ResponseData) => {
30+
data.statusText = message;
31+
return data;
32+
};
33+
}
34+
35+
delay(ms: number): HandlerResponseElement {
36+
return async (data: ResponseData) => {
37+
await delay(ms);
38+
return data;
39+
};
40+
}
41+
42+
json(value: any): HandlerResponseElement {
43+
return async (data: ResponseData) => {
44+
await this.header('Content-Type', 'application/json')(data);
45+
await this.body(JSON.stringify(value))(data);
46+
return data;
47+
};
48+
}
49+
50+
text(value: string): HandlerResponseElement {
51+
return async (data: ResponseData) => {
52+
await this.header('Content-Type', 'text/plain')(data);
53+
await this.body(value)(data);
54+
return data;
55+
};
56+
}
57+
58+
body(body: any): HandlerResponseElement {
59+
return async (data: ResponseData) => {
60+
data.body = body;
61+
return data;
62+
};
63+
}
64+
65+
header(key: string, value: string): HandlerResponseElement {
66+
return async (data: ResponseData) => {
67+
const headers = data.headers || new Headers();
68+
if (headers instanceof Headers) {
69+
headers.set(key, value);
70+
} else if (Array.isArray(headers)) {
71+
headers.push([key, value]);
72+
} else {
73+
headers[key] = value;
74+
}
75+
data.headers = headers;
76+
return data;
77+
};
78+
}
79+
80+
fetch(): Promise<Response> {
81+
return this.realFetch(this.req.input, this.req.init);
82+
}
83+
}
84+
85+
export default MockContext;

src/promise-utils.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/response-utils.ts

Lines changed: 0 additions & 96 deletions
This file was deleted.

0 commit comments

Comments
 (0)