Skip to content

Commit cc5412b

Browse files
authored
feat: Use a custom request spy to track requests (#9)
* feat: Use a custom request spy to track requests * chore: Improve README, add methods for createRequestSpy
1 parent a56efb4 commit cc5412b

File tree

8 files changed

+150
-35
lines changed

8 files changed

+150
-35
lines changed

README.md

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
# mocr [![npm][npm-image]][npm-url] [![github-ci][github-ci-image]][github-ci-url]
22

3-
A mock http server used in tests
3+
> A mock http server used in tests
4+
5+
<p align="center">
6+
<img src="https://user-images.githubusercontent.com/6333409/99885443-dcd40e00-2c2c-11eb-9261-6cfd3d7de5a0.png" alt="Mocr Logo" width="125" height="125" />
7+
</p>
48

59
## Features
610

711
- Easy to use, mock http server
8-
- Spy on the requests received by the server
12+
- Spy/track requests received by the server
913
- Designed to work with end-to-end & unit tests
1014
- Strongly typed, types included
1115
- Zero dependencies
@@ -22,19 +26,19 @@ npm install --save-dev mocr
2226

2327
All config options mentioned below are **_optional_**.
2428

25-
| Name | Default | Description |
26-
| ---------- | --------- | ------------------------------------------------------------------------------------------------------------ |
27-
| debug | false | When set to true, logging will be enabled. |
28-
| port | 9091 | The port that the server will be running. |
29-
| requestSpy | undefined | Can be a spy or a call. See [usage](#usage) below. If defined, will be called with `(request, requestBody)`. |
29+
| Name | Default | Description |
30+
| ---------- | --------- | ----------------------------------------------------------------------------------------------------------- |
31+
| debug | false | When set to true, logging will be enabled. |
32+
| port | 9091 | The port that the server will be running. |
33+
| requestSpy | undefined | This spy can track all the requests that reach the server. See [createRequestSpy](#createRequestSpy) below. |
3034

3135
## Usage
3236

3337
```js
34-
import mocr from 'mocr';
38+
import mocr, { createRequestSpy } from 'mocr';
3539

3640
describe('my tests', () => {
37-
const requestSpy = jest.fn();
41+
const requestSpy = createRequestSpy();
3842

3943
const mockServer = mocr({
4044
/* Configuration */
@@ -47,7 +51,7 @@ describe('my tests', () => {
4751

4852
beforeEach(async () => {
4953
// Reset the request spy
50-
requestSpy.mockReset();
54+
requestSpy.reset();
5155
});
5256

5357
afterAll(async () => {
@@ -56,25 +60,37 @@ describe('my tests', () => {
5660
});
5761

5862
it('should make a call to the backend when pressing the button', () => {
59-
const requestSpy = jest.fn();
60-
6163
// Press the button
6264

63-
const request = requestSpy.mock.calls[0][0];
64-
const requestBody = requestSpy.mock.calls[0][1];
65+
const { request, body } = requestSpy.calls[0];
6566

6667
expect(requestSpy).toHaveBeenCalledTimes(1);
6768
expect(request.method).toBe(/* Expected Method, ie. POST, PUT */);
68-
expect(requestBody).toHaveBeenCalledWith(/* Expected Request Body */);
69+
expect(body).toHaveBeenCalledWith(/* Expected Request Body */);
6970
});
7071
});
7172
```
7273

73-
[github-ci-image]: https://github.com/manosim/mocr/workflows/Run%20Tests/badge.svg
74-
[github-ci-url]: https://github.com/manosim/mocr/actions
75-
[npm-image]: https://badge.fury.io/js/mocr.svg
76-
[npm-url]: https://www.npmjs.com/package/mocr
74+
## Methods
75+
76+
### mocr
77+
78+
Used to create an instance of _mocr_ - it accepts _optional_ configuration. You can have as many _mocr_ servers running in parallel as long as they run on a [different port](#configuration).
79+
80+
### createRequestSpy
81+
82+
Creates a fresh request spy. This records/tracks all _incoming_ requests to the mock server along with their body/data(if any). To be used for validating requests/content leaving your application. Below you can find all available methods for a RequestSpy.
83+
84+
| Name | Description |
85+
| ----- | --------------------------------------------------------------------- |
86+
| calls | An array of all the calls. `[ {request: IncomingMessage. body: string | {} } ]` |
87+
| reset | Empties the `calls` array. |
7788

7889
## License
7990

8091
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
92+
93+
[github-ci-image]: https://github.com/manosim/mocr/workflows/Run%20Tests/badge.svg
94+
[github-ci-url]: https://github.com/manosim/mocr/actions
95+
[npm-image]: https://badge.fury.io/js/mocr.svg
96+
[npm-url]: https://www.npmjs.com/package/mocr

lib/index.test.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fetch from 'node-fetch';
22

33
import { Logger } from './logger';
4-
import { mocr } from '.';
4+
import { mocr, createRequestSpy } from '.';
55

66
describe('index.ts', () => {
77
const DEFAULT_SERVER_URL = 'http://localhost:9091';
@@ -64,17 +64,43 @@ describe('index.ts', () => {
6464
});
6565

6666
it('uses a spy for intercepting requests', async () => {
67-
const requestSpy = jest.fn();
67+
const requestSpy = createRequestSpy();
68+
69+
jest.spyOn(requestSpy, 'recordRequest');
70+
6871
const mockServer = mocr();
6972

7073
await mockServer.start(requestSpy);
7174

7275
await fetch(`${DEFAULT_SERVER_URL}/profile`);
7376

74-
const request = requestSpy.mock.calls[0][0];
77+
const { request } = requestSpy.calls[0];
78+
79+
expect(request.url).toBe('/profile');
80+
expect(requestSpy.recordRequest).toHaveBeenCalledTimes(1);
81+
82+
await mockServer.stop();
83+
});
84+
85+
it('uses a spy for intercepting requests with body(JSON)', async () => {
86+
const requestSpy = createRequestSpy();
87+
88+
jest.spyOn(requestSpy, 'recordRequest');
89+
90+
const mockServer = mocr();
91+
92+
await mockServer.start(requestSpy);
93+
94+
await fetch(`${DEFAULT_SERVER_URL}/profile`, {
95+
method: 'POST',
96+
body: 'Hello!',
97+
});
98+
99+
const { request, body } = requestSpy.calls[0];
75100

76101
expect(request.url).toBe('/profile');
77-
expect(requestSpy).toHaveBeenCalledTimes(1);
102+
expect(requestSpy.recordRequest).toHaveBeenCalledTimes(1);
103+
expect(body).toBe('Hello!');
78104

79105
await mockServer.stop();
80106
});

lib/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { IncomingMessage, Server } from 'http';
1+
import { Server } from 'http';
22
import { startServer } from './startServer';
33
import { stopServer } from './stopServer';
44

55
import { Logger } from './logger';
6-
import { Config } from './types';
6+
import { Config, RequestSpy } from './types';
77

88
const defaultConfig: Config = {
99
debug: false,
@@ -19,9 +19,7 @@ export const mocr = (initialConfig?: Config) => {
1919
let server: Server | undefined;
2020
let logger = new Logger(config.debug);
2121

22-
const start = async (
23-
requestSpy?: (req: IncomingMessage) => void
24-
): Promise<void> => {
22+
const start = async (requestSpy?: RequestSpy): Promise<void> => {
2523
server = await startServer({ config, logger, requestSpy });
2624
};
2725

@@ -35,4 +33,5 @@ export const mocr = (initialConfig?: Config) => {
3533
};
3634
};
3735

36+
export { createRequestSpy } from './requestSpy';
3837
export default mocr;

lib/requestSpy.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { IncomingMessage } from 'http';
2+
import { Socket } from 'net';
3+
4+
import { createRequestSpy } from './requestSpy';
5+
6+
describe('requestSpy.ts', () => {
7+
const mockIncomingRequest = new IncomingMessage(new Socket());
8+
9+
it('creates a request spy', async () => {
10+
const requestSpy = createRequestSpy();
11+
expect(requestSpy.calls).toHaveLength(0);
12+
expect(requestSpy.recordRequest).toBeDefined();
13+
expect(requestSpy.reset).toBeDefined();
14+
});
15+
16+
it('records a call', async () => {
17+
const requestSpy = createRequestSpy();
18+
requestSpy.recordRequest(mockIncomingRequest);
19+
expect(requestSpy.calls).toHaveLength(1);
20+
});
21+
22+
it('records a call with body(JSON)', async () => {
23+
const requestSpy = createRequestSpy();
24+
25+
requestSpy.recordRequest(mockIncomingRequest, { hello: 'world' });
26+
27+
expect(requestSpy.calls).toHaveLength(1);
28+
expect(requestSpy.calls[0].body).toEqual({ hello: 'world' });
29+
});
30+
31+
it('resets/clears all requests', async () => {
32+
const requestSpy = createRequestSpy();
33+
34+
requestSpy.recordRequest(mockIncomingRequest);
35+
requestSpy.reset();
36+
37+
expect(requestSpy.calls).toHaveLength(0);
38+
});
39+
});

lib/requestSpy.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { IncomingMessage } from 'http';
2+
import { RequestRecord, RequestSpy } from './types';
3+
4+
export const createRequestSpy: () => RequestSpy = () => {
5+
const calls: RequestRecord[] = [];
6+
7+
const reset = () => {
8+
calls.splice(0, calls.length);
9+
};
10+
11+
const recordRequest = (request: IncomingMessage, body?: string | {}) => {
12+
calls.push({ request, body });
13+
};
14+
15+
return {
16+
calls,
17+
reset,
18+
recordRequest,
19+
};
20+
};

lib/startServer.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fetch from 'node-fetch';
22

33
import { Logger } from './logger';
4+
import { createRequestSpy } from './requestSpy';
45
import { startServer } from './startServer';
56
import { stopServer } from './stopServer';
67

@@ -26,7 +27,7 @@ describe('startServer.ts', () => {
2627
});
2728

2829
it('validates the body(JSON) of a POST request using the listener', async () => {
29-
const requestSpy = jest.fn();
30+
const requestSpy = createRequestSpy();
3031

3132
const mockServer = await startServer({
3233
config: {
@@ -47,13 +48,13 @@ describe('startServer.ts', () => {
4748

4849
await stopServer(mockServer, mockLogger);
4950

50-
const requestBody = requestSpy.mock.calls[0][1];
51+
const requestBody = requestSpy.calls[0].body;
5152

5253
expect(requestBody).toEqual({ hello: 'world' });
5354
});
5455

5556
it('validates a GET request using the listener', async () => {
56-
const requestSpy = jest.fn();
57+
const requestSpy = createRequestSpy();
5758

5859
const mockServer = await startServer({
5960
config: {
@@ -68,8 +69,8 @@ describe('startServer.ts', () => {
6869

6970
await stopServer(mockServer, mockLogger);
7071

71-
const request = requestSpy.mock.calls[0][0];
72-
const requestBody = requestSpy.mock.calls[0][1];
72+
const request = requestSpy.calls[0].request;
73+
const requestBody = requestSpy.calls[0].body;
7374

7475
expect(request.method).toBe('GET');
7576
expect(requestBody).not.toBeDefined();

lib/startServer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ export const startServer = async ({
1919
req.on('end', () => {
2020
if (requestSpy) {
2121
const isReqJson = req.headers['content-type'] === 'application/json';
22-
requestSpy(req, isReqJson && body ? JSON.parse(body) : body);
22+
requestSpy.recordRequest(
23+
req,
24+
isReqJson && body ? JSON.parse(body) : body
25+
);
2326
}
2427

2528
res.statusCode = 200;

lib/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,16 @@ export type Config = {
99
export type StartOptions = {
1010
config: Config;
1111
logger: Logger;
12-
requestSpy?: (req: IncomingMessage, body?: string) => void;
12+
requestSpy?: RequestSpy;
13+
};
14+
15+
export type RequestRecord = {
16+
request: IncomingMessage;
17+
body?: string | {};
18+
};
19+
20+
export type RequestSpy = {
21+
calls: RequestRecord[];
22+
reset: () => void;
23+
recordRequest: (request: IncomingMessage, body?: string | {}) => void;
1324
};

0 commit comments

Comments
 (0)