Skip to content

Commit 43114e8

Browse files
authored
Merge pull request #13 from ericclemmons/13-rebrand
v2 Readme
2 parents 85b7afb + ff33b33 commit 43114e8

File tree

16 files changed

+331
-80
lines changed

16 files changed

+331
-80
lines changed

README.md

Lines changed: 186 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,204 @@
1-
![Back to the Fixture](./logo.jpg)
1+
<p align="center">
2+
<img alt="node-recorder logo" src="./logo.gif" width="50%">
3+
</p>
24

3-
> Simple recording &amp; replaying of HTTP requests for predictable development &amp; testing.
5+
- Spend less time writing mocks & fixtures.
6+
- Automatically record new HTTP(s) requests.
7+
- Replay fixtures when testing.
8+
- Works well with [supertest](https://github.com/visionmedia/supertest).
9+
- Predictable, deterministic filepaths that match the URL:
410

5-
## Example
11+
1. Call https://api.github.com/rate_limit.
12+
1. `./__fixtures__/api.github.com/rate_limit/${hash}.json`
613

7-
Given a GraphQL server:
14+
```json
15+
{
16+
"request": {
17+
"method": "GET",
18+
"href": "https://api.github.com/rate_limit",
19+
"headers": {...},
20+
"body": ""
21+
},
22+
"response": {
23+
"statusCode": 200,
24+
"headers": {...},
25+
"body": {...}
26+
}
27+
}
28+
```
29+
30+
* Normalize the `request` & `response`.
31+
* Alias cookies & Oauth tokens to users, to avoid ambiguity.
32+
* Ignore requests you don't want to record.
33+
34+
## Installation
35+
36+
```shell
37+
$ yarn add node-recorder --dev
38+
# or
39+
$ npm install node-recorder --save-dev
40+
```
41+
42+
## Getting Started
43+
44+
- By simply including `node-recorder`, **all HTTP(s) requests are intercepted**.
45+
- By default, `RECORD` mode records new fixtures, and replays existing fixures.
46+
- When in `NODE_ENV=test` or `CI=true`, `REPLAY` mode replays existing fixtures, and throws an error when one doesn't exist.
47+
_(So that local tests don't suddenly fail in CI)_
48+
49+
### Recorder Modes
50+
51+
- `bypass` - All network requests bypass the recorder and respond as usual.
52+
- `record` - Record only new network requests (i.e. those without fixtures), while replaying existing fixtures.
53+
- `replay` - Replay all network requests using fixtures. **If a fixture is missing, an error is thrown**.
54+
- `rerecord` - Re-record all network requests.
55+
56+
### Using `node --require`
57+
58+
```shell
59+
$ node -r node-recorder path/to/server.js
60+
```
61+
62+
_(This also works with `mocha`!)_
63+
64+
### Setting the `mode` via `RECORDER=...`
65+
66+
```shell
67+
$ RECORDER=ignore node -r node-recorder path/to/server.js
68+
```
69+
70+
### Using Jest
71+
72+
Included is a `jest-preset` that will automatically include `node-recorder` and a custom plugin to make toggling modes easier.
873

974
```js
10-
import { recorder } from "back-to-the-fixture";
11-
import graphql from "express-graphql";
75+
// jest.config.js
76+
module.exports = {
77+
preset: "node-recorder/jest-preset"
78+
};
79+
```
80+
81+
Now, running `jest --watch` will add a new `r` option:
1282

13-
export default graphql((req: Request, res: Response) => {
14-
// 👇 Pull ?mode=record or ?mode=replay
15-
const { mode } = req.query;
83+
```
84+
Watch Usage
85+
› Press a to run all tests.
86+
› Press f to run only failed tests.
87+
› Press p to filter by a filename regex pattern.
88+
› Press t to filter by a test name regex pattern.
89+
› Press q to quit watch mode.
90+
› Press r to change recording mode from "REPLAY".
91+
› Press Enter to trigger a test run.
92+
```
1693

17-
// 👇 Create a recorder for this request
18-
recorder.configure({ mode });
94+
Pressing `r` will toggle between the various modes:
1995

20-
return {
21-
graphiql: true,
22-
pretty: true,
23-
schema
24-
};
25-
});
96+
```
97+
╭─────────────────────────────╮
98+
│ │
99+
│ node-recorder: RECORD │
100+
│ │
101+
╰─────────────────────────────╯
26102
```
27103

28-
- **Record** network calls – <http://localhost:3000/?mode=record>
29-
- **Replay** network calls - <http://localhost:3000/?mode=replay>
104+
### Configuring `recorder.config.js`
30105

31-
Fixtures are stored based on their URL with the name `${hash}.${user}.json`:
106+
Within your project, you can create a `recorder.config.js` that exports:
32107

108+
```js
109+
// recorder.conig.js
110+
module.exports = {
111+
identify(request, response) {...},
112+
ignore(request) {...},
113+
normalize(request, response) {...}
114+
}
33115
```
34-
.
35-
└── __fixtures__
36-
   └── api.github.com
37-
   └── rate_limit
38-
   └── 4280543676.all.json
116+
117+
- `request` is the same as the fixture (e.g. `body`, `headers`, `href`, `method`), but
118+
with an additional `url` property from https://github.com/unshiftio/url-parse to simplify conditional logic.
119+
- `response` contains `body`, `headers`, & `statusCode`.
120+
121+
#### `identify` a `request` or `response
122+
123+
This is useful when network requests are stateful, in that they rely on an authorization call first, then they pass along a token/cookie to subsequent calls:
124+
125+
1. Suppose you login by calling `/login?user=foo&password=bar`.
126+
2. The response contains `{ "token": "abc123" }3. Now, to get data, you call`/api?token=abc123`.
127+
128+
When recording fixtures, the token `abc123` isn't clearly associated with the user `foo`.
129+
130+
To address this, you can `identify` the `request` and `response`, so that the fixtures are aliased accordingly:
131+
132+
```js
133+
identify(request, response) {
134+
const { user, token } = request.query
135+
136+
if (request.href.endsWith("/login")) {
137+
// We know the user, but not the token yet
138+
if (!response) {
139+
return user
140+
}
141+
142+
// Upon login, associate this `user` with the `token`
143+
return [user, response.body.token]
144+
}
145+
146+
// API calls supply a `token`, which has been associated with a `user`
147+
if (request.href.endsWith("/api")) {
148+
return token
149+
}
150+
}
39151
```
40152

41-
This way, similar requests for different users/logins in your testing can be
42-
easily found.
153+
Now, when recorded fixtures will look like:
43154

44-
## Installation
155+
- `127.0.0.1/login/${hash}.${user}.json`
156+
- `127.0.0.1/api/${hash}.${user}.json`
45157

46-
```shell
47-
yarn add --dev back-to-the-fixture
158+
This way, similar-looking network requests (e.g. login & GraphQL) can be differentiated and easily searched for.
159+
160+
#### `ignore` a `request`
161+
162+
Typically, you don't want to record fixtures for things like analytics or reporting.
163+
164+
```js
165+
// recorder.conig.js
166+
module.exports = {
167+
ignore(request) {
168+
if (request.href.includes("www.google-analytics.com")) {
169+
return true;
170+
}
171+
172+
return false;
173+
}
174+
};
48175
```
176+
177+
#### `normalize` a `request` or `response`
178+
179+
Fixtures are meant to make development & testing _easier_, so modification is necessary.
180+
181+
- **Changing `request` changes the filename `hash` of the fixture**. You may need to `record` again.
182+
- `normalize` is called **before** the network request and **after**. This means that `response` may be `undefined`!
183+
- You can **change `response` by hand, or via `normalize` without affecting the filename `hash` of the fixture**.
184+
185+
```js
186+
module.exports = {
187+
normalize(request, response) {
188+
// Suppose you never care about `user-agent`
189+
delete request.headers["user-agent"];
190+
191+
// We may not have a response (yet)
192+
if (response) {
193+
// ...or the `date`
194+
delete response;
195+
}
196+
}
197+
};
198+
```
199+
200+
## MIT License
201+
202+
## Author
203+
204+
- Eric Clemmons

example/__tests__/graphql.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe("/logout", () => {
2727
.get("/logout")
2828
.expect(
2929
"set-cookie",
30-
"back-to-the-fixture-example=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT"
30+
"node-recorder-example=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT"
3131
)
3232
.expect(302, "Found. Redirecting to /");
3333
});

example/routes/logout/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ import session from "../session";
55
export default express()
66
.use(session)
77
.use((req: express.Request, res: express.Response, next) => {
8-
res.clearCookie("back-to-the-fixture-example");
8+
res.clearCookie("node-recorder-example");
99
res.redirect("/");
1010
});

example/routes/session.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as session from "express-session";
44
export default express()
55
.use(
66
session({
7-
name: "back-to-the-fixture-example",
7+
name: "node-recorder-example",
88
resave: false,
99
saveUninitialized: false,
1010
secret: "secret"
@@ -13,10 +13,7 @@ export default express()
1313
.use((req, res, next) => {
1414
const cookie = req.get("cookie") || "";
1515

16-
if (
17-
!cookie.includes("back-to-the-fixture-example") &&
18-
req.path !== "/login"
19-
) {
16+
if (!cookie.includes("node-recorder-example") && req.path !== "/login") {
2017
return res.status(403).send(`<a href="/login">Login</a>`);
2118
}
2219

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
module.exports = {
22
// ! Required for recorder to run across all tests
3-
setupFiles: [require.resolve("./")],
3+
setupFiles: [require.resolve("./dist")],
44

55
// ! Required to prevent `rerecord` from triggering builds
66
// ? Commenting this out, since it doesn't seem to be a problem
77
// watchPathIgnorePatterns: [recorder.fixturesPath],
88

99
// ! Required for `r` shortcut
10-
watchPlugins: [require.resolve("./JestWatchPlugin")]
10+
watchPlugins: [require.resolve("./dist/JestWatchPlugin")]
1111
};

jest.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ module.exports = merge(
66
{
77
moduleNameMapper: {
88
// ! Don't use this, because Jest messes up polydev's require(...)
9-
// "back-to-the-fixture": "<rootDir>/dist"
9+
// "node-recorder": "<rootDir>/dist"
1010
},
1111
testEnvironment: "node"
1212
},
1313
require("ts-jest/jest-preset"),
1414

1515
// @ts-ignore
16-
require("./src/jest-preset")
16+
require("./jest-preset")
1717
);

logo.gif

28.3 KB
Loading

logo.html

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<!DOCTYPE html>
2+
3+
<head>
4+
<link href="https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" rel="stylesheet" />
5+
<style>
6+
blink {
7+
animation: blinking 1s infinite alternate-reverse ease-in-out;
8+
}
9+
10+
/* http://brenna.github.io/csshexagon/ */
11+
.hexagon {
12+
position: relative;
13+
width: 24px;
14+
height: 13.86px;
15+
background-color: #659f63;
16+
margin: 6.93px 0;
17+
18+
top: 1px;
19+
}
20+
21+
.hexagon:before,
22+
.hexagon:after {
23+
content: "";
24+
position: absolute;
25+
width: 0;
26+
border-left: 12px solid transparent;
27+
border-right: 12px solid transparent;
28+
}
29+
30+
.hexagon:before {
31+
bottom: 100%;
32+
border-bottom: 6.93px solid #659f63;
33+
}
34+
35+
.hexagon:after {
36+
top: 100%;
37+
width: 0;
38+
border-top: 6.93px solid #659f63;
39+
}
40+
41+
.play {
42+
display: none;
43+
}
44+
45+
.hexagon {
46+
display: none;
47+
}
48+
49+
@keyframes blinking {
50+
0% {
51+
opacity: 1;
52+
}
53+
54+
49% {
55+
opacity: 1;
56+
}
57+
58+
50% {
59+
opacity: 0;
60+
}
61+
62+
100% {
63+
opacity: 0;
64+
}
65+
}
66+
</style>
67+
</head>
68+
69+
<body>
70+
<main class="flex h-screen justify-center items-center p-32 bg-grey">
71+
<code class="bg-white rounded p-32 text-grey-darkest text-5xl flex justify-center items-center">
72+
<blink class="rounded-full h-4 w-4 bg-red mr-2"></blink>
73+
<span class="underline">Re</span>c<span
74+
class="play text-white h-6 w-6 bg-grey-darkest rounded-full flex items-center justify-center text-2xl pl-1"
75+
style="margin-top: 2px;">
76+
<span class="-ml-px"></span>
77+
</span><span class="hexagon"></span><span class="hidden mt-1 text-green"></span><span
78+
class="h-6 w-6 -mt-6 mx-px relative" style="top: -1px;">
79+
<svg version="1.1" viewBox="0 0 18 20" xmlns="http://www.w3.org/2000/svg">
80+
<g fill="none" fill-rule="evenodd">
81+
<g fill="#44883E">
82+
<path
83+
d="m18 14v-8c-7.321e-4 -0.7138-0.38183-1.3731-1-1.73l-7-4c-0.6188-0.35727-1.3812-0.35727-2 0l-7 4c-0.61817 0.3569-0.99927 1.0162-1 1.73v8c7.3215e-4 0.7138 0.38183 1.3731 1 1.73l7 4c0.6188 0.35727 1.3812 0.35727 2 0l7-4c0.61817-0.3569 0.99927-1.0162 1-1.73z" />
84+
</g>
85+
</g>
86+
</svg>
87+
</span>rder
88+
</code>
89+
</main>
90+
</body>

logo.jpg

-1.2 MB
Binary file not shown.

0 commit comments

Comments
 (0)