Skip to content

Commit 7110aac

Browse files
salmanmmcollina
authored andcommitted
feat: freeze proto to work with express (#67)
* feat: freeze proto to work with express * feat: conditionally fix proto methods for express mode * test: add express tests, run all test in test folder * chore: delete unused test * chore: typo * pr feedback * fixProto -> freezeProto * doc: update for express mode
1 parent d8cf6ea commit 7110aac

File tree

8 files changed

+128
-6
lines changed

8 files changed

+128
-6
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,6 @@ package-lock.json
5757

5858
# generated code
5959
examples/typescript-server.js
60-
test/type/index.js
60+
test/type/index.js
61+
62+
.vscode

README.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
[![Build Status](https://travis-ci.org/fastify/light-my-request.svg?branch=master)](https://travis-ci.org/fastify/light-my-request) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
66

7-
Injects a fake HTTP request/response into a node HTTP server for simulating server logic, writing tests, or debugging.
8-
Does not use a socket connection so can be run against an inactive server (server not in listen mode).
7+
Injects a fake HTTP request/response into a node HTTP server for simulating server logic, writing tests, or debugging.
8+
Does not use a socket connection so can be run against an inactive server (server not in listen mode).
99

1010
## Example
1111

@@ -131,6 +131,26 @@ The declaration file exports types for the following parts of the API:
131131
- `Request` - custom light-my-request `request` object interface. Extends Node.js `stream.Readable` type
132132
- `Response` - custom light-my-request `response` object interface. Extends Node.js `http.ServerResponse` type
133133

134+
### Example with Express
135+
136+
```javascript
137+
const http = require('http')
138+
const inject = require('light-my-request')
139+
140+
const dispatch = function (req, res) {
141+
const reply = 'Hello World'
142+
res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': reply.length })
143+
res.end(reply)
144+
}
145+
146+
const server = http.createServer(dispatch)
147+
148+
inject(dispatch, { method: 'get', url: '/', express: true }, (err, res) => {
149+
console.log(res.payload)
150+
})
151+
```
152+
Note the `express: true` in the above example. This flag makes sure that we still get the correct behaviour with express framework.
153+
134154
## API
135155

136156
#### `inject(dispatchFunc[, options, callback])`
@@ -159,6 +179,7 @@ Injects a fake request into an HTTP server.
159179
- `server` - Optional http server. It is used for binding the `dispatchFunc`.
160180
- `autoStart` - Automatically start the request as soon as the method
161181
is called. It is only valid when not passing a callback. Defaults to `true`.
182+
- `express` - A boolean flag to enable working with express framework. By default express doesn't support request injection, this enforces.
162183
- `callback` - the callback function using the signature `function (err, res)` where:
163184
- `err` - error object
164185
- `res` - a response object where:
@@ -225,8 +246,8 @@ inject(dispatch)
225246
Note: The application would not respond multiple times. If you try to invoking any method after the application has responded, the application would throw an error.
226247

227248
## Acknowledgements
228-
This project has been forked from [`hapi/shot`](https://github.com/hapijs/shot) because we wanted to support *Node ≥ v4* and not only *Node ≥ v8*.
229-
All the credits before the commit [00a2a82](https://github.com/fastify/light-my-request/commit/00a2a82eb773b765003b6085788cc3564cd08326) goes to the `hapi/shot` project [contributors](https://github.com/hapijs/shot/graphs/contributors).
249+
This project has been forked from [`hapi/shot`](https://github.com/hapijs/shot) because we wanted to support *Node ≥ v4* and not only *Node ≥ v8*.
250+
All the credits before the commit [00a2a82](https://github.com/fastify/light-my-request/commit/00a2a82eb773b765003b6085788cc3564cd08326) goes to the `hapi/shot` project [contributors](https://github.com/hapijs/shot/graphs/contributors).
230251
Since the commit [db8bced](https://github.com/fastify/light-my-request/commit/db8bced10b4367731688c8738621d42f39680efc) the project will be maintained by the Fastify team.
231252

232253
## License

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ declare namespace LightMyRequest {
5757
validate?: boolean
5858
payload?: InjectPayload
5959
server?: http.Server
60+
express?: boolean
6061
}
6162

6263
interface Request extends stream.Readable {

index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const http = require('http')
55
const Ajv = require('ajv')
66
const Request = require('./lib/request')
77
const Response = require('./lib/response')
8+
const freezeProto = require('./util')
89

910
const errorMessage = 'The dispatch function has already been invoked'
1011
const urlSchema = {
@@ -82,18 +83,29 @@ function doInject (dispatchFunc, options, callback) {
8283
}
8384
}
8485

86+
const express = options.express
8587
const server = options.server || {}
8688

8789
if (typeof callback === 'function') {
8890
const req = new Request(options)
8991
const res = new Response(req, callback)
9092

93+
if (express) {
94+
freezeProto(req)
95+
freezeProto(res)
96+
}
97+
9198
return req.prepare(() => dispatchFunc.call(server, req, res))
9299
} else {
93100
return new Promise((resolve, reject) => {
94101
const req = new Request(options)
95102
const res = new Response(req, resolve, reject)
96103

104+
if (express) {
105+
freezeProto(req)
106+
freezeProto(res)
107+
}
108+
97109
req.prepare(() => dispatchFunc.call(server, req, res))
98110
})
99111
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"types": "index.d.ts",
1111
"devDependencies": {
1212
"@types/node": "^12.11.6",
13+
"express": "^4.17.1",
1314
"form-data": "^3.0.0",
1415
"pre-commit": "^1.2.2",
1516
"standard": "^14.0.2",
@@ -19,7 +20,7 @@
1920
"scripts": {
2021
"test": "npm run lint && npm run unit && npm run tsd",
2122
"lint": "standard",
22-
"unit": "tap test/test.js",
23+
"unit": "tap test/**.test.js",
2324
"coverage": "npm run unit -- --cov --coverage-report=html",
2425
"tsd": "tsd"
2526
},

test/express.test.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict'
2+
3+
const test = require('tap').test
4+
const express = require('express')
5+
6+
const inject = require('../index')
7+
8+
test('works with express with callback', (t) => {
9+
t.plan(8)
10+
11+
const app = express()
12+
13+
app.get('/', (req, res) => {
14+
res.send({ ok: true, path: '/' })
15+
})
16+
17+
app.get('/blah', (req, res) => {
18+
res.send({ ok: true, path: '/blah' })
19+
})
20+
21+
inject(app, { url: '/', express: true }, (err, res) => {
22+
t.error(err)
23+
t.equal(res.statusCode, 200)
24+
t.deepEqual(res.json(), { ok: true, path: '/' })
25+
})
26+
27+
inject(app, { url: '/blah', express: true }, (err, res) => {
28+
t.error(err)
29+
t.equal(res.statusCode, 200)
30+
t.deepEqual(res.json(), { ok: true, path: '/blah' })
31+
})
32+
33+
inject(app, { url: '/bad-path', express: true }, (err, res) => {
34+
t.error(err)
35+
t.equal(res.statusCode, 404)
36+
})
37+
})
38+
39+
test('works with express with promise', (t) => {
40+
t.plan(5)
41+
42+
const app = express()
43+
44+
app.get('/', (req, res) => {
45+
res.send({ ok: true, path: '/' })
46+
})
47+
48+
app.get('/blah', (req, res) => {
49+
res.send({ ok: true, path: '/blah' })
50+
})
51+
52+
inject(app, { url: '/', express: true }).then((res) => {
53+
t.equal(res.statusCode, 200)
54+
t.deepEqual(res.json(), { ok: true, path: '/' })
55+
})
56+
57+
inject(app, { url: '/blah', express: true }).then((res) => {
58+
t.equal(res.statusCode, 200)
59+
t.deepEqual(res.json(), { ok: true, path: '/blah' })
60+
})
61+
62+
inject(app, { url: '/bad-path', express: true }).then((res) => {
63+
t.equal(res.statusCode, 404)
64+
})
65+
})
File renamed without changes.

util.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* This is needed for express as it replaces our prototype.
3+
* https://github.com/expressjs/express/blob/master/lib/middleware/init.js#L36
4+
* Which is why we loose our overridden prototype methods.
5+
* This function ensures that those remain intact.
6+
*
7+
* @param instance
8+
*/
9+
function freezeProto (instance) {
10+
Object.keys(Object.getPrototypeOf(instance)).forEach((method) => {
11+
Object.defineProperty(instance, method, {
12+
writable: false,
13+
configurable: false,
14+
readable: true,
15+
value: instance[method]
16+
})
17+
})
18+
}
19+
20+
module.exports = freezeProto

0 commit comments

Comments
 (0)