├── .commitlintrc.js ├── .editorconfig ├── .eslintrc ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── ci └── remove-deps-4-old-node.js ├── index.js ├── lib ├── agent.js └── test.js ├── package.json └── test ├── .eslintrc ├── fixtures ├── test_cert.pem └── test_key.pem ├── supertest.js └── throwError.js /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [*.{js,json}] 12 | indent_size = 2 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base/legacy", 3 | "env": { 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6 9 | }, 10 | "rules": { 11 | // disabled - disagree with airbnb 12 | "func-names": [0], 13 | "space-before-function-paren": [0], 14 | "consistent-return": [0], 15 | 16 | // Disabled but may want to refactor code eventually 17 | "no-use-before-define": [2, "nofunc"], 18 | "no-underscore-dangle": [0], 19 | 20 | // IMHO, more sensible overrides to existing airbnb error definitions 21 | "max-len": [2, 100, 4, {"ignoreComments": true, "ignoreUrls": true}], 22 | "no-unused-expressions": [2, { "allowShortCircuit": true, "allowTernary": true }] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | include: 16 | - node-version: 14.x 17 | - node-version: 16.x 18 | - node-version: 18.x 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | path: ~/.npm 27 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} 28 | - name: Install Dependencies On Node ${{ matrix.node-version }} 29 | run: yarn install 30 | - run: npm test 31 | - name: Coverage On Node ${{ matrix.node-version }} 32 | run: 33 | npm run coverage 34 | - name: Upload coverage to Codecov 35 | uses: codecov/codecov-action@v3 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS # 2 | ################### 3 | .DS_Store 4 | .idea 5 | Thumbs.db 6 | tmp/ 7 | temp/ 8 | 9 | 10 | # Node.js # 11 | ################### 12 | node_modules 13 | 14 | 15 | # NYC # 16 | ################### 17 | coverage 18 | *.lcov 19 | .nyc_output 20 | 21 | 22 | # Files # 23 | ################### 24 | *.log 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .eslintrc 3 | .travis.yml 4 | .idea 5 | .vscode 6 | .nyc_output 7 | test 8 | coverage 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 TJ Holowaychuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [SuperTest](https://ladjs.github.io/superagent/) 2 | 3 | [![code coverage][coverage-badge]][coverage] 4 | [![Build Status][travis-badge]][travis] 5 | [![Dependencies][dependencies-badge]][dependencies] 6 | [![PRs Welcome][prs-badge]][prs] 7 | [![MIT License][license-badge]][license] 8 | 9 | > HTTP assertions made easy via [superagent](http://github.com/ladjs/superagent). Maintained for [Forward Email](https://github.com/forwardemail) and [Lad](https://github.com/ladjs). 10 | 11 | ## About 12 | 13 | The motivation with this module is to provide a high-level abstraction for testing 14 | HTTP, while still allowing you to drop down to the [lower-level API](https://ladjs.github.io/superagent/) provided by superagent. 15 | 16 | ## Getting Started 17 | 18 | Install SuperTest as an npm module and save it to your package.json file as a development dependency: 19 | 20 | ```bash 21 | npm install supertest --save-dev 22 | ``` 23 | 24 | Once installed it can now be referenced by simply calling ```require('supertest');``` 25 | 26 | ## Example 27 | 28 | You may pass an `http.Server`, or a `Function` to `request()` - if the server is not 29 | already listening for connections then it is bound to an ephemeral port for you so 30 | there is no need to keep track of ports. 31 | 32 | SuperTest works with any test framework, here is an example without using any 33 | test framework at all: 34 | 35 | ```js 36 | const request = require('supertest'); 37 | const express = require('express'); 38 | 39 | const app = express(); 40 | 41 | app.get('/user', function(req, res) { 42 | res.status(200).json({ name: 'john' }); 43 | }); 44 | 45 | request(app) 46 | .get('/user') 47 | .expect('Content-Type', /json/) 48 | .expect('Content-Length', '15') 49 | .expect(200) 50 | .end(function(err, res) { 51 | if (err) throw err; 52 | }); 53 | ``` 54 | 55 | To enable http2 protocol, simply append an options to `request` or `request.agent`: 56 | 57 | ```js 58 | const request = require('supertest'); 59 | const express = require('express'); 60 | 61 | const app = express(); 62 | 63 | app.get('/user', function(req, res) { 64 | res.status(200).json({ name: 'john' }); 65 | }); 66 | 67 | request(app, { http2: true }) 68 | .get('/user') 69 | .expect('Content-Type', /json/) 70 | .expect('Content-Length', '15') 71 | .expect(200) 72 | .end(function(err, res) { 73 | if (err) throw err; 74 | }); 75 | 76 | request.agent(app, { http2: true }) 77 | .get('/user') 78 | .expect('Content-Type', /json/) 79 | .expect('Content-Length', '15') 80 | .expect(200) 81 | .end(function(err, res) { 82 | if (err) throw err; 83 | }); 84 | ``` 85 | 86 | Here's an example with mocha, note how you can pass `done` straight to any of the `.expect()` calls: 87 | 88 | ```js 89 | describe('GET /user', function() { 90 | it('responds with json', function(done) { 91 | request(app) 92 | .get('/user') 93 | .set('Accept', 'application/json') 94 | .expect('Content-Type', /json/) 95 | .expect(200, done); 96 | }); 97 | }); 98 | ``` 99 | 100 | You can use `auth` method to pass HTTP username and password in the same way as in the [superagent](http://ladjs.github.io/superagent/#authentication): 101 | 102 | ```js 103 | describe('GET /user', function() { 104 | it('responds with json', function(done) { 105 | request(app) 106 | .get('/user') 107 | .auth('username', 'password') 108 | .set('Accept', 'application/json') 109 | .expect('Content-Type', /json/) 110 | .expect(200, done); 111 | }); 112 | }); 113 | ``` 114 | 115 | One thing to note with the above statement is that superagent now sends any HTTP 116 | error (anything other than a 2XX response code) to the callback as the first argument if 117 | you do not add a status code expect (i.e. `.expect(302)`). 118 | 119 | If you are using the `.end()` method `.expect()` assertions that fail will 120 | not throw - they will return the assertion as an error to the `.end()` callback. In 121 | order to fail the test case, you will need to rethrow or pass `err` to `done()`, as follows: 122 | 123 | ```js 124 | describe('POST /users', function() { 125 | it('responds with json', function(done) { 126 | request(app) 127 | .post('/users') 128 | .send({name: 'john'}) 129 | .set('Accept', 'application/json') 130 | .expect('Content-Type', /json/) 131 | .expect(200) 132 | .end(function(err, res) { 133 | if (err) return done(err); 134 | return done(); 135 | }); 136 | }); 137 | }); 138 | ``` 139 | 140 | You can also use promises: 141 | 142 | ```js 143 | describe('GET /users', function() { 144 | it('responds with json', function() { 145 | return request(app) 146 | .get('/users') 147 | .set('Accept', 'application/json') 148 | .expect('Content-Type', /json/) 149 | .expect(200) 150 | .then(response => { 151 | expect(response.body.email).toEqual('foo@bar.com'); 152 | }) 153 | }); 154 | }); 155 | ``` 156 | 157 | Or async/await syntax: 158 | 159 | ```js 160 | describe('GET /users', function() { 161 | it('responds with json', async function() { 162 | const response = await request(app) 163 | .get('/users') 164 | .set('Accept', 'application/json') 165 | expect(response.headers["Content-Type"]).toMatch(/json/); 166 | expect(response.status).toEqual(200); 167 | expect(response.body.email).toEqual('foo@bar.com'); 168 | }); 169 | }); 170 | ``` 171 | 172 | Expectations are run in the order of definition. This characteristic can be used 173 | to modify the response body or headers before executing an assertion. 174 | 175 | ```js 176 | describe('POST /user', function() { 177 | it('user.name should be an case-insensitive match for "john"', function(done) { 178 | request(app) 179 | .post('/user') 180 | .send('name=john') // x-www-form-urlencoded upload 181 | .set('Accept', 'application/json') 182 | .expect(function(res) { 183 | res.body.id = 'some fixed id'; 184 | res.body.name = res.body.name.toLowerCase(); 185 | }) 186 | .expect(200, { 187 | id: 'some fixed id', 188 | name: 'john' 189 | }, done); 190 | }); 191 | }); 192 | ``` 193 | 194 | Anything you can do with superagent, you can do with supertest - for example multipart file uploads! 195 | 196 | ```js 197 | request(app) 198 | .post('/') 199 | .field('name', 'my awesome avatar') 200 | .field('complex_object', '{"attribute": "value"}', {contentType: 'application/json'}) 201 | .attach('avatar', 'test/fixtures/avatar.jpg') 202 | ... 203 | ``` 204 | 205 | Passing the app or url each time is not necessary, if you're testing 206 | the same host you may simply re-assign the request variable with the 207 | initialization app or url, a new `Test` is created per `request.VERB()` call. 208 | 209 | ```js 210 | request = request('http://localhost:5555'); 211 | 212 | request.get('/').expect(200, function(err){ 213 | console.log(err); 214 | }); 215 | 216 | request.get('/').expect('heya', function(err){ 217 | console.log(err); 218 | }); 219 | ``` 220 | 221 | Here's an example with mocha that shows how to persist a request and its cookies: 222 | 223 | ```js 224 | const request = require('supertest'); 225 | const should = require('should'); 226 | const express = require('express'); 227 | const cookieParser = require('cookie-parser'); 228 | 229 | describe('request.agent(app)', function() { 230 | const app = express(); 231 | app.use(cookieParser()); 232 | 233 | app.get('/', function(req, res) { 234 | res.cookie('cookie', 'hey'); 235 | res.send(); 236 | }); 237 | 238 | app.get('/return', function(req, res) { 239 | if (req.cookies.cookie) res.send(req.cookies.cookie); 240 | else res.send(':(') 241 | }); 242 | 243 | const agent = request.agent(app); 244 | 245 | it('should save cookies', function(done) { 246 | agent 247 | .get('/') 248 | .expect('set-cookie', 'cookie=hey; Path=/', done); 249 | }); 250 | 251 | it('should send cookies', function(done) { 252 | agent 253 | .get('/return') 254 | .expect('hey', done); 255 | }); 256 | }); 257 | ``` 258 | 259 | There is another example that is introduced by the file [agency.js](https://github.com/ladjs/superagent/blob/master/test/node/agency.js) 260 | 261 | Here is an example where 2 cookies are set on the request. 262 | 263 | ```js 264 | agent(app) 265 | .get('/api/content') 266 | .set('Cookie', ['nameOne=valueOne;nameTwo=valueTwo']) 267 | .send() 268 | .expect(200) 269 | .end((err, res) => { 270 | if (err) { 271 | return done(err); 272 | } 273 | expect(res.text).to.be.equal('hey'); 274 | return done(); 275 | }); 276 | ``` 277 | 278 | ## API 279 | 280 | You may use any [superagent](http://github.com/ladjs/superagent) methods, 281 | including `.write()`, `.pipe()` etc and perform assertions in the `.end()` callback 282 | for lower-level needs. 283 | 284 | ### .expect(status[, fn]) 285 | 286 | Assert response `status` code. 287 | 288 | ### .expect(status, body[, fn]) 289 | 290 | Assert response `status` code and `body`. 291 | 292 | ### .expect(body[, fn]) 293 | 294 | Assert response `body` text with a string, regular expression, or 295 | parsed body object. 296 | 297 | ### .expect(field, value[, fn]) 298 | 299 | Assert header `field` `value` with a string or regular expression. 300 | 301 | ### .expect(function(res) {}) 302 | 303 | Pass a custom assertion function. It'll be given the response object to check. If the check fails, throw an error. 304 | 305 | ```js 306 | request(app) 307 | .get('/') 308 | .expect(hasPreviousAndNextKeys) 309 | .end(done); 310 | 311 | function hasPreviousAndNextKeys(res) { 312 | if (!('next' in res.body)) throw new Error("missing next key"); 313 | if (!('prev' in res.body)) throw new Error("missing prev key"); 314 | } 315 | ``` 316 | 317 | ### .end(fn) 318 | 319 | Perform the request and invoke `fn(err, res)`. 320 | 321 | ## Notes 322 | 323 | Inspired by [api-easy](https://github.com/flatiron/api-easy) minus vows coupling. 324 | 325 | ## License 326 | 327 | MIT 328 | 329 | [coverage-badge]: https://img.shields.io/codecov/c/github/ladjs/supertest.svg 330 | [coverage]: https://codecov.io/gh/ladjs/supertest 331 | [travis-badge]: https://travis-ci.org/ladjs/supertest.svg?branch=master 332 | [travis]: https://travis-ci.org/ladjs/supertest 333 | [dependencies-badge]: https://david-dm.org/ladjs/supertest/status.svg 334 | [dependencies]: https://david-dm.org/ladjs/supertest 335 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 336 | [prs]: http://makeapullrequest.com 337 | [license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square 338 | [license]: https://github.com/ladjs/supertest/blob/master/LICENSE 339 | -------------------------------------------------------------------------------- /ci/remove-deps-4-old-node.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const package = require('../package.json'); 4 | 5 | const UNSUPPORT_DEPS_4_OLD = { 6 | 'eslint': undefined, 7 | 'mocha': '6.x' 8 | }; 9 | 10 | const deps = Object.keys(UNSUPPORT_DEPS_4_OLD); 11 | for (const item in package.devDependencies) { 12 | if (deps.includes(item)) { 13 | package.devDependencies[item] = UNSUPPORT_DEPS_4_OLD[item]; 14 | } 15 | } 16 | 17 | delete package.scripts.lint; 18 | 19 | fs.writeFileSync( 20 | path.join(__dirname, '../package.json'), 21 | JSON.stringify(package, null, 2) 22 | ); 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const methods = require('methods'); 7 | let http2; 8 | try { 9 | http2 = require('http2'); // eslint-disable-line global-require 10 | } catch (_) { 11 | // eslint-disable-line no-empty 12 | } 13 | const Test = require('./lib/test.js'); 14 | const agent = require('./lib/agent.js'); 15 | 16 | /** 17 | * Test against the given `app`, 18 | * returning a new `Test`. 19 | * 20 | * @param {Function|Server|String} app 21 | * @return {Test} 22 | * @api public 23 | */ 24 | module.exports = function(app, options = {}) { 25 | const obj = {}; 26 | 27 | if (typeof app === 'function') { 28 | if (options.http2) { 29 | if (!http2) { 30 | throw new Error( 31 | 'supertest: this version of Node.js does not support http2' 32 | ); 33 | } 34 | } 35 | } 36 | 37 | methods.forEach(function(method) { 38 | obj[method] = function(url) { 39 | var test = new Test(app, method, url, options.http2); 40 | if (options.http2) { 41 | test.http2(); 42 | } 43 | return test; 44 | }; 45 | }); 46 | 47 | // Support previous use of del 48 | obj.del = obj.delete; 49 | 50 | return obj; 51 | }; 52 | 53 | /** 54 | * Expose `Test` 55 | */ 56 | module.exports.Test = Test; 57 | 58 | /** 59 | * Expose the agent function 60 | */ 61 | module.exports.agent = agent; 62 | -------------------------------------------------------------------------------- /lib/agent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const { agent: Agent } = require('superagent'); 8 | const methods = require('methods'); 9 | const http = require('http'); 10 | let http2; 11 | try { 12 | http2 = require('http2'); // eslint-disable-line global-require 13 | } catch (_) { 14 | // eslint-disable-line no-empty 15 | } 16 | const Test = require('./test.js'); 17 | 18 | /** 19 | * Initialize a new `TestAgent`. 20 | * 21 | * @param {Function|Server} app 22 | * @param {Object} options 23 | * @api public 24 | */ 25 | 26 | function TestAgent(app, options = {}) { 27 | if (!(this instanceof TestAgent)) return new TestAgent(app, options); 28 | 29 | const agent = new Agent(options); 30 | Object.assign(this, agent); 31 | 32 | this._options = options; 33 | 34 | if (typeof app === 'function') { 35 | if (options.http2) { 36 | if (!http2) { 37 | throw new Error( 38 | 'supertest: this version of Node.js does not support http2' 39 | ); 40 | } 41 | app = http2.createServer(app); // eslint-disable-line no-param-reassign 42 | } else { 43 | app = http.createServer(app); // eslint-disable-line no-param-reassign 44 | } 45 | } 46 | this.app = app; 47 | } 48 | 49 | /** 50 | * Inherits from `Agent.prototype`. 51 | */ 52 | 53 | Object.setPrototypeOf(TestAgent.prototype, Agent.prototype); 54 | 55 | // set a host name 56 | TestAgent.prototype.host = function(host) { 57 | this._host = host; 58 | return this; 59 | }; 60 | 61 | // override HTTP verb methods 62 | methods.forEach(function(method) { 63 | TestAgent.prototype[method] = function(url, fn) { // eslint-disable-line no-unused-vars 64 | const req = new Test(this.app, method.toUpperCase(), url); 65 | if (this._options.http2) { 66 | req.http2(); 67 | } 68 | 69 | if (this._host) { 70 | req.set('host', this._host); 71 | } 72 | 73 | req.on('response', this._saveCookies.bind(this)); 74 | req.on('redirect', this._saveCookies.bind(this)); 75 | req.on('redirect', this._attachCookies.bind(this, req)); 76 | this._setDefaults(req); 77 | this._attachCookies(req); 78 | 79 | return req; 80 | }; 81 | }); 82 | 83 | TestAgent.prototype.del = TestAgent.prototype.delete; 84 | 85 | /** 86 | * Expose `Agent`. 87 | */ 88 | 89 | module.exports = TestAgent; 90 | -------------------------------------------------------------------------------- /lib/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const { inspect } = require('util'); 8 | const http = require('http'); 9 | const { STATUS_CODES } = require('http'); 10 | const { Server } = require('tls'); 11 | const { deepStrictEqual } = require('assert'); 12 | const { Request } = require('superagent'); 13 | let http2; 14 | try { 15 | http2 = require('http2'); // eslint-disable-line global-require 16 | } catch (_) { 17 | // eslint-disable-line no-empty 18 | } 19 | 20 | /** @typedef {import('superagent').Response} Response */ 21 | 22 | class Test extends Request { 23 | /** 24 | * Initialize a new `Test` with the given `app`, 25 | * request `method` and `path`. 26 | * 27 | * @param {Server} app 28 | * @param {String} method 29 | * @param {String} path 30 | * @api public 31 | */ 32 | constructor (app, method, path, optHttp2) { 33 | super(method.toUpperCase(), path); 34 | 35 | if (typeof app === 'function') { 36 | if (optHttp2) { 37 | app = http2.createServer(app); // eslint-disable-line no-param-reassign 38 | } else { 39 | app = http.createServer(app); // eslint-disable-line no-param-reassign 40 | } 41 | } 42 | 43 | this.redirects(0); 44 | this.buffer(); 45 | this.app = app; 46 | this._asserts = []; 47 | this.url = typeof app === 'string' 48 | ? app + path 49 | : this.serverAddress(app, path); 50 | } 51 | 52 | /** 53 | * Returns a URL, extracted from a server. 54 | * 55 | * @param {Server} app 56 | * @param {String} path 57 | * @returns {String} URL address 58 | * @api private 59 | */ 60 | serverAddress(app, path) { 61 | const addr = app.address(); 62 | 63 | if (!addr) this._server = app.listen(0); 64 | const port = app.address().port; 65 | const protocol = app instanceof Server ? 'https' : 'http'; 66 | return protocol + '://127.0.0.1:' + port + path; 67 | } 68 | 69 | /** 70 | * Expectations: 71 | * 72 | * .expect(200) 73 | * .expect(200, fn) 74 | * .expect(200, body) 75 | * .expect('Some body') 76 | * .expect('Some body', fn) 77 | * .expect(['json array body', { key: 'val' }]) 78 | * .expect('Content-Type', 'application/json') 79 | * .expect('Content-Type', 'application/json', fn) 80 | * .expect(fn) 81 | * .expect([200, 404]) 82 | * 83 | * @return {Test} 84 | * @api public 85 | */ 86 | expect(a, b, c) { 87 | // callback 88 | if (typeof a === 'function') { 89 | this._asserts.push(wrapAssertFn(a)); 90 | return this; 91 | } 92 | if (typeof b === 'function') this.end(b); 93 | if (typeof c === 'function') this.end(c); 94 | 95 | // status 96 | if (typeof a === 'number') { 97 | this._asserts.push(wrapAssertFn(this._assertStatus.bind(this, a))); 98 | // body 99 | if (typeof b !== 'function' && arguments.length > 1) { 100 | this._asserts.push(wrapAssertFn(this._assertBody.bind(this, b))); 101 | } 102 | return this; 103 | } 104 | 105 | // multiple statuses 106 | if (Array.isArray(a) && a.length > 0 && a.every(val => typeof val === 'number')) { 107 | this._asserts.push(wrapAssertFn(this._assertStatusArray.bind(this, a))); 108 | return this; 109 | } 110 | 111 | // header field 112 | if (typeof b === 'string' || typeof b === 'number' || b instanceof RegExp) { 113 | this._asserts.push(wrapAssertFn(this._assertHeader.bind(this, { name: '' + a, value: b }))); 114 | return this; 115 | } 116 | 117 | // body 118 | this._asserts.push(wrapAssertFn(this._assertBody.bind(this, a))); 119 | 120 | return this; 121 | } 122 | 123 | /** 124 | * Defer invoking superagent's `.end()` until 125 | * the server is listening. 126 | * 127 | * @param {Function} fn 128 | * @api public 129 | */ 130 | end(fn) { 131 | const server = this._server; 132 | 133 | super.end((err, res) => { 134 | const localAssert = () => { 135 | this.assert(err, res, fn); 136 | }; 137 | 138 | if (server && server._handle) return server.close(localAssert); 139 | 140 | localAssert(); 141 | }); 142 | 143 | return this; 144 | } 145 | 146 | /** 147 | * Perform assertions and invoke `fn(err, res)`. 148 | * 149 | * @param {?Error} resError 150 | * @param {Response} res 151 | * @param {Function} fn 152 | * @api private 153 | */ 154 | assert(resError, res, fn) { 155 | let errorObj; 156 | 157 | // check for unexpected network errors or server not running/reachable errors 158 | // when there is no response and superagent sends back a System Error 159 | // do not check further for other asserts, if any, in such case 160 | // https://nodejs.org/api/errors.html#errors_common_system_errors 161 | const sysErrors = { 162 | ECONNREFUSED: 'Connection refused', 163 | ECONNRESET: 'Connection reset by peer', 164 | EPIPE: 'Broken pipe', 165 | ETIMEDOUT: 'Operation timed out' 166 | }; 167 | 168 | if (!res && resError) { 169 | if (resError instanceof Error && resError.syscall === 'connect' 170 | && Object.getOwnPropertyNames(sysErrors).indexOf(resError.code) >= 0) { 171 | errorObj = new Error(resError.code + ': ' + sysErrors[resError.code]); 172 | } else { 173 | errorObj = resError; 174 | } 175 | } 176 | 177 | // asserts 178 | for (let i = 0; i < this._asserts.length && !errorObj; i += 1) { 179 | errorObj = this._assertFunction(this._asserts[i], res); 180 | } 181 | 182 | // set unexpected superagent error if no other error has occurred. 183 | if (!errorObj && resError instanceof Error && (!res || resError.status !== res.status)) { 184 | errorObj = resError; 185 | } 186 | 187 | fn.call(this, errorObj || null, res); 188 | } 189 | 190 | /** 191 | * Perform assertions on a response body and return an Error upon failure. 192 | * 193 | * @param {Mixed} body 194 | * @param {Response} res 195 | * @return {?Error} 196 | * @api private 197 | */// eslint-disable-next-line class-methods-use-this 198 | _assertBody(body, res) { 199 | const isRegexp = body instanceof RegExp; 200 | 201 | // parsed 202 | if (typeof body === 'object' && !isRegexp) { 203 | try { 204 | deepStrictEqual(body, res.body); 205 | } catch (err) { 206 | const a = inspect(body); 207 | const b = inspect(res.body); 208 | return error('expected ' + a + ' response body, got ' + b, body, res.body); 209 | } 210 | } else if (body !== res.text) { 211 | // string 212 | const a = inspect(body); 213 | const b = inspect(res.text); 214 | 215 | // regexp 216 | if (isRegexp) { 217 | if (!body.test(res.text)) { 218 | return error('expected body ' + b + ' to match ' + body, body, res.body); 219 | } 220 | } else { 221 | return error('expected ' + a + ' response body, got ' + b, body, res.body); 222 | } 223 | } 224 | } 225 | 226 | /** 227 | * Perform assertions on a response header and return an Error upon failure. 228 | * 229 | * @param {Object} header 230 | * @param {Response} res 231 | * @return {?Error} 232 | * @api private 233 | */// eslint-disable-next-line class-methods-use-this 234 | _assertHeader(header, res) { 235 | const field = header.name; 236 | const actual = res.header[field.toLowerCase()]; 237 | const fieldExpected = header.value; 238 | 239 | if (typeof actual === 'undefined') return new Error('expected "' + field + '" header field'); 240 | // This check handles header values that may be a String or single element Array 241 | if ((Array.isArray(actual) && actual.toString() === fieldExpected) 242 | || fieldExpected === actual) { 243 | return; 244 | } 245 | if (fieldExpected instanceof RegExp) { 246 | if (!fieldExpected.test(actual)) { 247 | return new Error('expected "' + field + '" matching ' 248 | + fieldExpected + ', got "' + actual + '"'); 249 | } 250 | } else { 251 | return new Error('expected "' + field + '" of "' + fieldExpected + '", got "' + actual + '"'); 252 | } 253 | } 254 | 255 | /** 256 | * Perform assertions on the response status and return an Error upon failure. 257 | * 258 | * @param {Number} status 259 | * @param {Response} res 260 | * @return {?Error} 261 | * @api private 262 | */// eslint-disable-next-line class-methods-use-this 263 | _assertStatus(status, res) { 264 | if (res.status !== status) { 265 | const a = STATUS_CODES[status]; 266 | const b = STATUS_CODES[res.status]; 267 | return new Error('expected ' + status + ' "' + a + '", got ' + res.status + ' "' + b + '"'); 268 | } 269 | } 270 | 271 | /** 272 | * Perform assertions on the response status and return an Error upon failure. 273 | * 274 | * @param {Array} statusArray 275 | * @param {Response} res 276 | * @return {?Error} 277 | * @api private 278 | */// eslint-disable-next-line class-methods-use-this 279 | _assertStatusArray(statusArray, res) { 280 | if (!statusArray.includes(res.status)) { 281 | const b = STATUS_CODES[res.status]; 282 | const expectedList = statusArray.join(', '); 283 | return new Error( 284 | 'expected one of "' + expectedList + '", got ' + res.status + ' "' + b + '"' 285 | ); 286 | } 287 | } 288 | 289 | /** 290 | * Performs an assertion by calling a function and return an Error upon failure. 291 | * 292 | * @param {Function} fn 293 | * @param {Response} res 294 | * @return {?Error} 295 | * @api private 296 | */// eslint-disable-next-line class-methods-use-this 297 | _assertFunction(fn, res) { 298 | let err; 299 | try { 300 | err = fn(res); 301 | } catch (e) { 302 | err = e; 303 | } 304 | if (err instanceof Error) return err; 305 | } 306 | } 307 | 308 | /** 309 | * Wraps an assert function into another. 310 | * The wrapper function edit the stack trace of any assertion error, prepending a more useful stack to it. 311 | * 312 | * @param {Function} assertFn 313 | * @returns {Function} wrapped assert function 314 | */ 315 | 316 | function wrapAssertFn(assertFn) { 317 | const savedStack = new Error().stack.split('\n').slice(3); 318 | 319 | return function(res) { 320 | let badStack; 321 | let err; 322 | try { 323 | err = assertFn(res); 324 | } catch (e) { 325 | err = e; 326 | } 327 | if (err instanceof Error && err.stack) { 328 | badStack = err.stack.replace(err.message, '').split('\n').slice(1); 329 | err.stack = [err.toString()] 330 | .concat(savedStack) 331 | .concat('----') 332 | .concat(badStack) 333 | .join('\n'); 334 | } 335 | return err; 336 | }; 337 | } 338 | 339 | /** 340 | * Return an `Error` with `msg` and results properties. 341 | * 342 | * @param {String} msg 343 | * @param {Mixed} expected 344 | * @param {Mixed} actual 345 | * @return {Error} 346 | * @api private 347 | */ 348 | 349 | function error(msg, expected, actual) { 350 | const err = new Error(msg); 351 | err.expected = expected; 352 | err.actual = actual; 353 | err.showDiff = true; 354 | return err; 355 | } 356 | 357 | /** 358 | * Expose `Test`. 359 | */ 360 | 361 | module.exports = Test; 362 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supertest", 3 | "description": "SuperAgent driven library for testing HTTP servers", 4 | "version": "7.1.1", 5 | "author": "TJ Holowaychuk", 6 | "contributors": [], 7 | "dependencies": { 8 | "methods": "^1.1.2", 9 | "superagent": "^10.2.1" 10 | }, 11 | "devDependencies": { 12 | "@commitlint/cli": "17", 13 | "@commitlint/config-conventional": "17", 14 | "body-parser": "^1.20.2", 15 | "cookie-parser": "^1.4.6", 16 | "eslint": "^8.32.0", 17 | "eslint-config-airbnb-base": "^15.0.0", 18 | "eslint-plugin-import": "^2.27.5", 19 | "express": "^4.18.2", 20 | "mocha": "^10.2.0", 21 | "nock": "^13.3.0", 22 | "nyc": "^15.1.0", 23 | "proxyquire": "^2.1.3", 24 | "should": "^13.2.3" 25 | }, 26 | "engines": { 27 | "node": ">=14.18.0" 28 | }, 29 | "files": [ 30 | "index.js", 31 | "lib" 32 | ], 33 | "keywords": [ 34 | "bdd", 35 | "http", 36 | "request", 37 | "superagent", 38 | "tdd", 39 | "test", 40 | "testing" 41 | ], 42 | "license": "MIT", 43 | "main": "index.js", 44 | "repository": { 45 | "type": "git", 46 | "url": "https://github.com/ladjs/supertest.git" 47 | }, 48 | "scripts": { 49 | "coverage": "nyc report --reporter=text-lcov > coverage.lcov", 50 | "lint": "eslint lib/**/*.js test/**/*.js index.js", 51 | "lint:fix": "eslint --fix lib/**/*.js test/**/*.js index.js", 52 | "pretest": "npm run lint --if-present", 53 | "test": "nyc --reporter=html --reporter=text mocha --exit --require should --reporter spec --check-leaks" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | // errors - disabled for chai test support 4 | "no-unused-expressions": [0], 5 | // allow function args for superagent 6 | "no-unused-vars": [2, {"args": "none"}], 7 | // allow updates to response for certain tests 8 | "no-param-reassign": [2, {"props": false}] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/test_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDCTCCAfGgAwIBAgIUZtrgyKVudIs9Y90tCSeQHUjKy2IwDQYJKoZIhvcNAQEL 3 | BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIwMTAwOTIwMDkyOFoXDTIwMTEw 4 | ODIwMDkyOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 5 | AAOCAQ8AMIIBCgKCAQEAuU6E5t0+OT01AoLAEZ6HndwOwmZO3C/YhiyObKDGaxRi 6 | WIaTa52sADMj+JSNL2fnY6XS9SjJddK3PSbGstKJrdR0kmkwvzeZ090bMb3UHjSy 7 | b571s2VKCWfc8XoGsJfpHTnTk+bk0QKKVTfcd4ORPvXMG6sNAENHzbG0EyYX1dJ7 8 | DF1SfBC2spMlQ2s8eBTVO2wnK9pucgKgXSQNa31l+G2Ixf94HjrJA/YyTmqo7UuW 9 | D1ACxvxIKnzMVaeE2nMcRjb7SYBly41Z5A0mZ5mj1C7iQBM1cVn7FAK/5RYT3XJU 10 | qOejQy17K4O1B1gB+62X42lLdo4uN8/uX96/hzAmOQIDAQABo1MwUTAdBgNVHQ4E 11 | FgQUMY736EgCf9E/UitPXmJHR85Yy9EwHwYDVR0jBBgwFoAUMY736EgCf9E/UitP 12 | XmJHR85Yy9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAGA5Q 13 | CNQCrmTfd2cNckssngiC8kYCssaSloLpjmOl4PoCzT8Ggrer5OAHdywZExhK4BvG 14 | xycn1TwJWpm0rqUgisQy4NiqNUC7xIphYcWW668OSfW2ZW83/EHWEf4kPR9lnJUI 15 | W4cMrRd1XKIRAyuePGlgya3CoELlbgw2UYz6SLae6SjYReo10hWDRVj8+Z+P68ST 16 | WmDvg3tnbkSz9gOy/Pm+qgq5DMkKp6yJ0GyhlTRgIdYi3DtFizzEnSDBP1RlGRo5 17 | U9cyGCjNA9R9PlgY30tCvH33urPW0OWH+kFj7i8ksUJJKI4s4pTb2HpvdTeQvcG7 18 | 7+Jp8RcI+sxqFT4jyw== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/fixtures/test_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5ToTm3T45PTUC 3 | gsARnoed3A7CZk7cL9iGLI5soMZrFGJYhpNrnawAMyP4lI0vZ+djpdL1KMl10rc9 4 | Jsay0omt1HSSaTC/N5nT3RsxvdQeNLJvnvWzZUoJZ9zxegawl+kdOdOT5uTRAopV 5 | N9x3g5E+9cwbqw0AQ0fNsbQTJhfV0nsMXVJ8ELaykyVDazx4FNU7bCcr2m5yAqBd 6 | JA1rfWX4bYjF/3geOskD9jJOaqjtS5YPUALG/EgqfMxVp4TacxxGNvtJgGXLjVnk 7 | DSZnmaPULuJAEzVxWfsUAr/lFhPdclSo56NDLXsrg7UHWAH7rZfjaUt2ji43z+5f 8 | 3r+HMCY5AgMBAAECggEBALh3iaWoqMCiRZryPfFMNwTWg3rSDb7zgkBPKpjIk70U 9 | 1bH6hdajZw3r2usiNknyzU1NTevvZl18Hh0p9LMfEx+QV1tIi9ZOqztU6DVkGzzW 10 | iKrFOyISutkSI8ffCbnR/6WwYwbg2veV589dhIMU3gom9cC1ToPsdhY1yGUnjqKy 11 | 6CGvwA8qae4lV1BJVZi3aVmd278WVhBphF12gKGYkNjSBaasuTvABIwUMH+sjKiP 12 | 9UjxNsrHVO9RSWmZdygr9vpDHnwyE+1Pm9Pd5FR8xit5U+PZM71jsV5CJuADB6wO 13 | bUe/qIUJCCfQPk4rjvkVaVD8xX6xK2/RGRCKJ8YHXAECgYEA9fangEVlC1qgfVaK 14 | khI/CwyJ4RekUf1a2EXH3QQflfR5fwNcAZ7Rgt7oQ2IIRmk0qLp3lGjuNQ9VF58i 15 | JdSlzvVQnlclJVTE++mQDuJitYZ+p+WCwoCNRM/vnMABGEUcopothiNY2AilJNHh 16 | nMrVI1ZMqasoIfaxfuUPdUSzQBECgYEAwN49zbKIaXs8L3v9fdhFGNDGQFCZ2qHM 17 | ZaaO5PACnB6P74uhfE2mfJ/zS4udcnlt/CUSsgBDgSvEsX/rXDDkAGnBAQC7T2is 18 | hKO3ClOUb8MghNN/L2QamZDwffPqOnn0eE3GEq8Qs2TSbA0+Bt8lm1uVRs67PKAP 19 | rYjsY5eYK6kCgYEAglu9nsAos4HOuV8ahhxhiUuV79SF5GZwtVsWeE7tJp6xnd17 20 | 7+fqhn/5fW0Bkb/EhwB8zA1o4npD0QcoJAC1+CAQIDtzlnt9Az5geWMGicrEadu8 21 | F7XmKWhDSEKC0ggfCxbHteYZ+jVqwT7zYhQmLlpYuzvZQ1bp76UbMj28+uECgYAO 22 | YEJxE66xVhs9WtuhRr6Xw/ATGS7uqgLHTOv3yqAXLPwDmf/WeR9AyNdkuSpqPvzg 23 | v46uL/DYLwABTwynGYnVMgzN21Ua7S120ZEyNtqonf3NiMpBKRAGhFQ4vzalVzPO 24 | x9VMzTnMdWZt4WrPLlDqTKBK39v6/99LSxp7rfAMyQKBgFAbAjdW8mNRqr2c562e 25 | rL894oKVOcnuPLovEx5pHWW3NOdPSdEjA5q4aISw4F6YnUXyCAGqbAOp+GrS3xXz 26 | xKj8qta/mqvHjj95EoMybnUwaCgK3dw0+QyuXFNxtejbOD1Ubljutn8hzswGryVg 27 | Z70KXTszjaQxgrTZpmKljS88 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/supertest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const https = require('https'); 4 | let http2; 5 | try { 6 | http2 = require('http2'); // eslint-disable-line global-require 7 | } catch (_) { 8 | // eslint-disable-line no-empty 9 | } 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | const should = require('should'); 13 | const express = require('express'); 14 | const bodyParser = require('body-parser'); 15 | const cookieParser = require('cookie-parser'); 16 | const nock = require('nock'); 17 | const request = require('../index.js'); 18 | const throwError = require('./throwError'); 19 | 20 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 21 | 22 | function shouldIncludeStackWithThisFile(err) { 23 | err.stack.should.match(/test\/supertest.js:/); 24 | err.stack.should.startWith(err.name + ':'); 25 | } 26 | 27 | describe('request(url)', function () { 28 | it('should be supported', function (done) { 29 | const app = express(); 30 | let server; 31 | 32 | app.get('/', function (req, res) { 33 | res.send('hello'); 34 | }); 35 | 36 | server = app.listen(function () { 37 | const url = 'http://127.0.0.1:' + server.address().port; 38 | request(url) 39 | .get('/') 40 | .expect('hello', done); 41 | }); 42 | }); 43 | 44 | describe('.end(cb)', function () { 45 | it('should set `this` to the test object when calling cb', function (done) { 46 | const app = express(); 47 | let server; 48 | 49 | app.get('/', function (req, res) { 50 | res.send('hello'); 51 | }); 52 | 53 | server = app.listen(function () { 54 | const url = 'http://127.0.0.1:' + server.address().port; 55 | const test = request(url).get('/'); 56 | test.end(function (err, res) { 57 | this.should.eql(test); 58 | done(); 59 | }); 60 | }); 61 | }); 62 | }); 63 | }); 64 | 65 | describe('request(app)', function () { 66 | it('should fire up the app on an ephemeral port', function (done) { 67 | const app = express(); 68 | 69 | app.get('/', function (req, res) { 70 | res.send('hey'); 71 | }); 72 | 73 | request(app) 74 | .get('/') 75 | .end(function (err, res) { 76 | res.status.should.equal(200); 77 | res.text.should.equal('hey'); 78 | done(); 79 | }); 80 | }); 81 | 82 | it('should not ECONNRESET on multiple simultaneous tests', function (done) { 83 | const app = express(); 84 | 85 | app.get('/', function (req, res) { 86 | res.send('hey'); 87 | }); 88 | 89 | const test = request(app); 90 | 91 | const requestCount = 10; 92 | 93 | const requests = []; 94 | for (let i = 0; i < requestCount; i += 1) requests.push(test.get('/')); 95 | 96 | global.Promise.all(requests).then(() => done(), done); 97 | }); 98 | 99 | it('should work with an active server', function (done) { 100 | const app = express(); 101 | let server; 102 | 103 | app.get('/', function (req, res) { 104 | res.send('hey'); 105 | }); 106 | 107 | server = app.listen(function () { 108 | request(server) 109 | .get('/') 110 | .end(function (err, res) { 111 | res.status.should.equal(200); 112 | res.text.should.equal('hey'); 113 | done(); 114 | }); 115 | }); 116 | }); 117 | 118 | it('should work with remote server', function (done) { 119 | const app = express(); 120 | let server; 121 | 122 | app.get('/', function (req, res) { 123 | res.send('hey'); 124 | }); 125 | 126 | server = app.listen(function () { 127 | const url = 'http://127.0.0.1:' + server.address().port; 128 | request(url) 129 | .get('/') 130 | .end(function (err, res) { 131 | res.status.should.equal(200); 132 | res.text.should.equal('hey'); 133 | done(); 134 | }); 135 | }); 136 | }); 137 | 138 | it('should work with a https server', function (done) { 139 | const app = express(); 140 | const fixtures = path.join(__dirname, 'fixtures'); 141 | const server = https.createServer({ 142 | key: fs.readFileSync(path.join(fixtures, 'test_key.pem')), 143 | cert: fs.readFileSync(path.join(fixtures, 'test_cert.pem')) 144 | }, app); 145 | 146 | app.get('/', function (req, res) { 147 | res.send('hey'); 148 | }); 149 | 150 | request(server) 151 | .get('/') 152 | .end(function (err, res) { 153 | if (err) return done(err); 154 | res.status.should.equal(200); 155 | res.text.should.equal('hey'); 156 | done(); 157 | }); 158 | }); 159 | 160 | it('should work with .send() etc', function (done) { 161 | const app = express(); 162 | 163 | app.use(bodyParser.json()); 164 | 165 | app.post('/', function (req, res) { 166 | res.send(req.body.name); 167 | }); 168 | 169 | request(app) 170 | .post('/') 171 | .send({ name: 'john' }) 172 | .expect('john', done); 173 | }); 174 | 175 | it('should work when unbuffered', function (done) { 176 | const app = express(); 177 | 178 | app.get('/', function (req, res) { 179 | res.end('Hello'); 180 | }); 181 | 182 | request(app) 183 | .get('/') 184 | .expect('Hello', done); 185 | }); 186 | 187 | it('should default redirects to 0', function (done) { 188 | const app = express(); 189 | 190 | app.get('/', function (req, res) { 191 | res.redirect('/login'); 192 | }); 193 | 194 | request(app) 195 | .get('/') 196 | .expect(302, done); 197 | }); 198 | 199 | it('should handle redirects', function (done) { 200 | const app = express(); 201 | 202 | app.get('/login', function (req, res) { 203 | res.end('Login'); 204 | }); 205 | 206 | app.get('/', function (req, res) { 207 | res.redirect('/login'); 208 | }); 209 | 210 | request(app) 211 | .get('/') 212 | .redirects(1) 213 | .end(function (err, res) { 214 | should.exist(res); 215 | res.status.should.be.equal(200); 216 | res.text.should.be.equal('Login'); 217 | done(); 218 | }); 219 | }); 220 | 221 | it('should handle socket errors', function (done) { 222 | const app = express(); 223 | 224 | app.get('/', function (req, res) { 225 | res.destroy(); 226 | }); 227 | 228 | request(app) 229 | .get('/') 230 | .end(function (err) { 231 | should.exist(err); 232 | done(); 233 | }); 234 | }); 235 | 236 | describe('.end(fn)', function () { 237 | it('should close server', function (done) { 238 | const app = express(); 239 | let test; 240 | 241 | app.get('/', function (req, res) { 242 | res.send('supertest FTW!'); 243 | }); 244 | 245 | test = request(app) 246 | .get('/') 247 | .end(function () { 248 | }); 249 | 250 | test._server.on('close', function () { 251 | done(); 252 | }); 253 | }); 254 | 255 | it('should wait for server to close before invoking fn', function (done) { 256 | const app = express(); 257 | let closed = false; 258 | let test; 259 | 260 | app.get('/', function (req, res) { 261 | res.send('supertest FTW!'); 262 | }); 263 | 264 | test = request(app) 265 | .get('/') 266 | .end(function () { 267 | closed.should.be.true; 268 | done(); 269 | }); 270 | 271 | test._server.on('close', function () { 272 | closed = true; 273 | }); 274 | }); 275 | 276 | it('should support nested requests', function (done) { 277 | const app = express(); 278 | const test = request(app); 279 | 280 | app.get('/', function (req, res) { 281 | res.send('supertest FTW!'); 282 | }); 283 | 284 | test 285 | .get('/') 286 | .end(function () { 287 | test 288 | .get('/') 289 | .end(function (err, res) { 290 | (err === null).should.be.true; 291 | res.status.should.equal(200); 292 | res.text.should.equal('supertest FTW!'); 293 | done(); 294 | }); 295 | }); 296 | }); 297 | 298 | it('should include the response in the error callback', function (done) { 299 | const app = express(); 300 | 301 | app.get('/', function (req, res) { 302 | res.send('whatever'); 303 | }); 304 | 305 | request(app) 306 | .get('/') 307 | .expect(function () { 308 | throw new Error('Some error'); 309 | }) 310 | .end(function (err, res) { 311 | should.exist(err); 312 | should.exist(res); 313 | // Duck-typing response, just in case. 314 | res.status.should.equal(200); 315 | done(); 316 | }); 317 | }); 318 | 319 | it('should set `this` to the test object when calling the error callback', function (done) { 320 | const app = express(); 321 | let test; 322 | 323 | app.get('/', function (req, res) { 324 | res.send('whatever'); 325 | }); 326 | 327 | test = request(app).get('/'); 328 | test.expect(function () { 329 | throw new Error('Some error'); 330 | }).end(function (err, res) { 331 | should.exist(err); 332 | this.should.eql(test); 333 | done(); 334 | }); 335 | }); 336 | 337 | it('should handle an undefined Response', function (done) { 338 | const app = express(); 339 | let server; 340 | 341 | app.get('/', function (req, res) { 342 | setTimeout(function () { 343 | res.end(); 344 | }, 20); 345 | }); 346 | 347 | server = app.listen(function () { 348 | const url = 'http://127.0.0.1:' + server.address().port; 349 | request(url) 350 | .get('/') 351 | .timeout(1) 352 | .expect(200, function (err) { 353 | err.should.be.an.instanceof(Error); 354 | return done(); 355 | }); 356 | }); 357 | }); 358 | 359 | it('should handle error returned when server goes down', function (done) { 360 | const app = express(); 361 | let server; 362 | 363 | app.get('/', function (req, res) { 364 | res.end(); 365 | }); 366 | 367 | server = app.listen(function () { 368 | const url = 'http://127.0.0.1:' + server.address().port; 369 | server.close(); 370 | request(url) 371 | .get('/') 372 | .expect(200, function (err) { 373 | err.should.be.an.instanceof(Error); 374 | return done(); 375 | }); 376 | }); 377 | }); 378 | }); 379 | 380 | describe('.expect(status[, fn])', function () { 381 | it('should assert the response status', function (done) { 382 | const app = express(); 383 | 384 | app.get('/', function (req, res) { 385 | res.send('hey'); 386 | }); 387 | 388 | request(app) 389 | .get('/') 390 | .expect(404) 391 | .end(function (err, res) { 392 | err.message.should.equal('expected 404 "Not Found", got 200 "OK"'); 393 | shouldIncludeStackWithThisFile(err); 394 | done(); 395 | }); 396 | }); 397 | }); 398 | 399 | describe('.expect(status)', function () { 400 | it('should handle connection error', function (done) { 401 | const req = request.agent('http://127.0.0.1:1234'); 402 | 403 | req 404 | .get('/') 405 | .expect(200) 406 | .end(function (err, res) { 407 | err.message.should.equal('ECONNREFUSED: Connection refused'); 408 | done(); 409 | }); 410 | }); 411 | }); 412 | 413 | describe('.expect(status)', function () { 414 | it('should assert only status', function (done) { 415 | const app = express(); 416 | 417 | app.get('/', function (req, res) { 418 | res.send('hey'); 419 | }); 420 | 421 | request(app) 422 | .get('/') 423 | .expect(200) 424 | .end(done); 425 | }); 426 | }); 427 | 428 | describe('.expect(statusArray)', function () { 429 | it('should assert only status', function (done) { 430 | const app = express(); 431 | 432 | app.get('/', function (req, res) { 433 | res.send('hey'); 434 | }); 435 | 436 | request(app) 437 | .get('/') 438 | .expect([200, 404]) 439 | .end(done); 440 | }); 441 | 442 | it('should reject if status is not in valid statuses array', function (done) { 443 | const app = express(); 444 | 445 | app.get('/', function (req, res) { 446 | res.send('hey'); 447 | }); 448 | 449 | request(app) 450 | .get('/') 451 | .expect([500, 404]) 452 | .end(function (err, res) { 453 | err.message.should.equal('expected one of "500, 404", got 200 "OK"'); 454 | shouldIncludeStackWithThisFile(err); 455 | done(); 456 | }); 457 | }); 458 | }); 459 | 460 | describe('.expect(status, body[, fn])', function () { 461 | it('should assert the response body and status', function (done) { 462 | const app = express(); 463 | 464 | app.get('/', function (req, res) { 465 | res.send('foo'); 466 | }); 467 | 468 | request(app) 469 | .get('/') 470 | .expect(200, 'foo', done); 471 | }); 472 | 473 | describe('when the body argument is an empty string', function () { 474 | it('should not quietly pass on failure', function (done) { 475 | const app = express(); 476 | 477 | app.get('/', function (req, res) { 478 | res.send('foo'); 479 | }); 480 | 481 | request(app) 482 | .get('/') 483 | .expect(200, '') 484 | .end(function (err, res) { 485 | err.message.should.equal('expected \'\' response body, got \'foo\''); 486 | shouldIncludeStackWithThisFile(err); 487 | done(); 488 | }); 489 | }); 490 | }); 491 | }); 492 | 493 | describe('.expect(body[, fn])', function () { 494 | it('should assert the response body', function (done) { 495 | const app = express(); 496 | 497 | app.set('json spaces', 0); 498 | 499 | app.get('/', function (req, res) { 500 | res.send({ foo: 'bar' }); 501 | }); 502 | 503 | request(app) 504 | .get('/') 505 | .expect('hey') 506 | .end(function (err, res) { 507 | err.message.should.equal('expected \'hey\' response body, got \'{"foo":"bar"}\''); 508 | shouldIncludeStackWithThisFile(err); 509 | done(); 510 | }); 511 | }); 512 | 513 | it('should assert the status before the body', function (done) { 514 | const app = express(); 515 | 516 | app.set('json spaces', 0); 517 | 518 | app.get('/', function (req, res) { 519 | res.status(500).send({ message: 'something went wrong' }); 520 | }); 521 | 522 | request(app) 523 | .get('/') 524 | .expect(200) 525 | .expect('hey') 526 | .end(function (err, res) { 527 | err.message.should.equal('expected 200 "OK", got 500 "Internal Server Error"'); 528 | shouldIncludeStackWithThisFile(err); 529 | done(); 530 | }); 531 | }); 532 | 533 | it('should assert the response text', function (done) { 534 | const app = express(); 535 | 536 | app.set('json spaces', 0); 537 | 538 | app.get('/', function (req, res) { 539 | res.send({ foo: 'bar' }); 540 | }); 541 | 542 | request(app) 543 | .get('/') 544 | .expect('{"foo":"bar"}', done); 545 | }); 546 | 547 | it('should assert the parsed response body', function (done) { 548 | const app = express(); 549 | 550 | app.set('json spaces', 0); 551 | 552 | app.get('/', function (req, res) { 553 | res.send({ foo: 'bar' }); 554 | }); 555 | 556 | request(app) 557 | .get('/') 558 | .expect({ foo: 'baz' }) 559 | .end(function (err, res) { 560 | err.message.should.equal('expected { foo: \'baz\' } response body, got { foo: \'bar\' }'); 561 | shouldIncludeStackWithThisFile(err); 562 | 563 | request(app) 564 | .get('/') 565 | .expect({ foo: 'bar' }) 566 | .end(done); 567 | }); 568 | }); 569 | 570 | it('should test response object types', function (done) { 571 | const app = express(); 572 | app.get('/', function (req, res) { 573 | res.status(200).json({ stringValue: 'foo', numberValue: 3 }); 574 | }); 575 | 576 | request(app) 577 | .get('/') 578 | .expect({ stringValue: 'foo', numberValue: 3 }, done); 579 | }); 580 | 581 | it('should deep test response object types', function (done) { 582 | const app = express(); 583 | app.get('/', function (req, res) { 584 | res.status(200) 585 | .json({ stringValue: 'foo', numberValue: 3, nestedObject: { innerString: '5' } }); 586 | }); 587 | 588 | request(app) 589 | .get('/') 590 | .expect({ stringValue: 'foo', numberValue: 3, nestedObject: { innerString: 5 } }) 591 | .end(function (err, res) { 592 | err.message.replace(/[^a-zA-Z]/g, '').should.equal('expected {\n stringValue: \'foo\',\n numberValue: 3,\n nestedObject: { innerString: 5 }\n} response body, got {\n stringValue: \'foo\',\n numberValue: 3,\n nestedObject: { innerString: \'5\' }\n}'.replace(/[^a-zA-Z]/g, '')); // eslint-disable-line max-len 593 | shouldIncludeStackWithThisFile(err); 594 | 595 | request(app) 596 | .get('/') 597 | .expect({ stringValue: 'foo', numberValue: 3, nestedObject: { innerString: '5' } }) 598 | .end(done); 599 | }); 600 | }); 601 | 602 | it('should support parsed response arrays', function (done) { 603 | const app = express(); 604 | app.get('/', function (req, res) { 605 | res.status(200).json(['a', { id: 1 }]); 606 | }); 607 | 608 | request(app) 609 | .get('/') 610 | .expect(['a', { id: 1 }], done); 611 | }); 612 | 613 | it('should support empty array responses', function (done) { 614 | const app = express(); 615 | app.get('/', function (req, res) { 616 | res.status(200).json([]); 617 | }); 618 | 619 | request(app) 620 | .get('/') 621 | .expect([], done); 622 | }); 623 | 624 | it('should support regular expressions', function (done) { 625 | const app = express(); 626 | 627 | app.get('/', function (req, res) { 628 | res.send('foobar'); 629 | }); 630 | 631 | request(app) 632 | .get('/') 633 | .expect(/^bar/) 634 | .end(function (err, res) { 635 | err.message.should.equal('expected body \'foobar\' to match /^bar/'); 636 | shouldIncludeStackWithThisFile(err); 637 | done(); 638 | }); 639 | }); 640 | 641 | it('should assert response body multiple times', function (done) { 642 | const app = express(); 643 | 644 | app.get('/', function (req, res) { 645 | res.send('hey tj'); 646 | }); 647 | 648 | request(app) 649 | .get('/') 650 | .expect(/tj/) 651 | .expect('hey') 652 | .expect('hey tj') 653 | .end(function (err, res) { 654 | err.message.should.equal("expected 'hey' response body, got 'hey tj'"); 655 | shouldIncludeStackWithThisFile(err); 656 | done(); 657 | }); 658 | }); 659 | 660 | it('should assert response body multiple times with no exception', function (done) { 661 | const app = express(); 662 | 663 | app.get('/', function (req, res) { 664 | res.send('hey tj'); 665 | }); 666 | 667 | request(app) 668 | .get('/') 669 | .expect(/tj/) 670 | .expect(/^hey/) 671 | .expect('hey tj', done); 672 | }); 673 | }); 674 | 675 | describe('.expect(field, value[, fn])', function () { 676 | it('should assert the header field presence', function (done) { 677 | const app = express(); 678 | 679 | app.get('/', function (req, res) { 680 | res.send({ foo: 'bar' }); 681 | }); 682 | 683 | request(app) 684 | .get('/') 685 | .expect('Content-Foo', 'bar') 686 | .end(function (err, res) { 687 | err.message.should.equal('expected "Content-Foo" header field'); 688 | shouldIncludeStackWithThisFile(err); 689 | done(); 690 | }); 691 | }); 692 | 693 | it('should assert the header field value', function (done) { 694 | const app = express(); 695 | 696 | app.get('/', function (req, res) { 697 | res.send({ foo: 'bar' }); 698 | }); 699 | 700 | request(app) 701 | .get('/') 702 | .expect('Content-Type', 'text/html') 703 | .end(function (err, res) { 704 | err.message.should.equal('expected "Content-Type" of "text/html", ' 705 | + 'got "application/json; charset=utf-8"'); 706 | shouldIncludeStackWithThisFile(err); 707 | done(); 708 | }); 709 | }); 710 | 711 | it('should assert multiple fields', function (done) { 712 | const app = express(); 713 | 714 | app.get('/', function (req, res) { 715 | res.send('hey'); 716 | }); 717 | 718 | request(app) 719 | .get('/') 720 | .expect('Content-Type', 'text/html; charset=utf-8') 721 | .expect('Content-Length', '3') 722 | .end(done); 723 | }); 724 | 725 | it('should support regular expressions', function (done) { 726 | const app = express(); 727 | 728 | app.get('/', function (req, res) { 729 | res.send('hey'); 730 | }); 731 | 732 | request(app) 733 | .get('/') 734 | .expect('Content-Type', /^application/) 735 | .end(function (err) { 736 | err.message.should.equal('expected "Content-Type" matching /^application/, ' 737 | + 'got "text/html; charset=utf-8"'); 738 | shouldIncludeStackWithThisFile(err); 739 | done(); 740 | }); 741 | }); 742 | 743 | it('should support numbers', function (done) { 744 | const app = express(); 745 | 746 | app.get('/', function (req, res) { 747 | res.send('hey'); 748 | }); 749 | 750 | request(app) 751 | .get('/') 752 | .expect('Content-Length', 4) 753 | .end(function (err) { 754 | err.message.should.equal('expected "Content-Length" of "4", got "3"'); 755 | shouldIncludeStackWithThisFile(err); 756 | done(); 757 | }); 758 | }); 759 | 760 | describe('handling arbitrary expect functions', function () { 761 | let app; 762 | let get; 763 | 764 | before(function () { 765 | app = express(); 766 | app.get('/', function (req, res) { 767 | res.send('hey'); 768 | }); 769 | }); 770 | 771 | beforeEach(function () { 772 | get = request(app).get('/'); 773 | }); 774 | 775 | it('reports errors', function (done) { 776 | get 777 | .expect(throwError('failed')) 778 | .end(function (err) { 779 | err.message.should.equal('failed'); 780 | shouldIncludeStackWithThisFile(err); 781 | done(); 782 | }); 783 | }); 784 | 785 | // this scenario should never happen after https://github.com/ladjs/supertest/pull/767 786 | // meant for test coverage for lib/test.js#287 787 | // https://github.com/ladjs/supertest/blob/e064b5ae71e1dfa3e1a74745fda527ac542e1878/lib/test.js#L287 788 | it('_assertFunction should catch and return error', function (done) { 789 | const error = new Error('failed'); 790 | const returnedError = get 791 | // private api 792 | ._assertFunction(function (res) { 793 | throw error; 794 | }); 795 | get 796 | .end(function () { 797 | returnedError.should.equal(error); 798 | returnedError.message.should.equal('failed'); 799 | shouldIncludeStackWithThisFile(returnedError); 800 | done(); 801 | }); 802 | }); 803 | 804 | it( 805 | 'ensures truthy non-errors returned from asserts are not promoted to errors', 806 | function (done) { 807 | get 808 | .expect(function (res) { 809 | return 'some descriptive error'; 810 | }) 811 | .end(function (err) { 812 | should.not.exist(err); 813 | done(); 814 | }); 815 | } 816 | ); 817 | 818 | it('ensures truthy errors returned from asserts are throw to end', function (done) { 819 | get 820 | .expect(throwError('some descriptive error')) 821 | .end(function (err) { 822 | err.message.should.equal('some descriptive error'); 823 | shouldIncludeStackWithThisFile(err); 824 | (err instanceof Error).should.be.true; 825 | done(); 826 | }); 827 | }); 828 | 829 | it("doesn't create false negatives", function (done) { 830 | get 831 | .expect(function (res) { 832 | }) 833 | .end(done); 834 | }); 835 | 836 | it("doesn't create false negatives on non error objects", function (done) { 837 | const handler = { 838 | get: function(target, prop, receiver) { 839 | throw Error('Should not be called for non Error objects'); 840 | } 841 | }; 842 | const proxy = new Proxy({}, handler); // eslint-disable-line no-undef 843 | get 844 | .expect(() => proxy) 845 | .end(done); 846 | }); 847 | 848 | it('handles multiple asserts', function (done) { 849 | const calls = []; 850 | get 851 | .expect(function (res) { 852 | calls[0] = 1; 853 | }) 854 | .expect(function (res) { 855 | calls[1] = 1; 856 | }) 857 | .expect(function (res) { 858 | calls[2] = 1; 859 | }) 860 | .end(function () { 861 | const callCount = [0, 1, 2].reduce(function (count, i) { 862 | return count + calls[i]; 863 | }, 0); 864 | callCount.should.equal(3, "didn't see all assertions run"); 865 | done(); 866 | }); 867 | }); 868 | 869 | it('plays well with normal assertions - no false positives', function (done) { 870 | get 871 | .expect(function (res) { 872 | }) 873 | .expect('Content-Type', /json/) 874 | .end(function (err) { 875 | err.message.should.match(/Content-Type/); 876 | shouldIncludeStackWithThisFile(err); 877 | done(); 878 | }); 879 | }); 880 | 881 | it('plays well with normal assertions - no false negatives', function (done) { 882 | get 883 | .expect(function (res) { 884 | }) 885 | .expect('Content-Type', /html/) 886 | .expect(function (res) { 887 | }) 888 | .expect('Content-Type', /text/) 889 | .end(done); 890 | }); 891 | }); 892 | 893 | describe('handling multiple assertions per field', function () { 894 | it('should work', function (done) { 895 | const app = express(); 896 | app.get('/', function (req, res) { 897 | res.send('hey'); 898 | }); 899 | 900 | request(app) 901 | .get('/') 902 | .expect('Content-Type', /text/) 903 | .expect('Content-Type', /html/) 904 | .end(done); 905 | }); 906 | 907 | it('should return an error if the first one fails', function (done) { 908 | const app = express(); 909 | app.get('/', function (req, res) { 910 | res.send('hey'); 911 | }); 912 | 913 | request(app) 914 | .get('/') 915 | .expect('Content-Type', /bloop/) 916 | .expect('Content-Type', /html/) 917 | .end(function (err) { 918 | err.message.should.equal('expected "Content-Type" matching /bloop/, ' 919 | + 'got "text/html; charset=utf-8"'); 920 | shouldIncludeStackWithThisFile(err); 921 | done(); 922 | }); 923 | }); 924 | 925 | it('should return an error if a middle one fails', function (done) { 926 | const app = express(); 927 | app.get('/', function (req, res) { 928 | res.send('hey'); 929 | }); 930 | 931 | request(app) 932 | .get('/') 933 | .expect('Content-Type', /text/) 934 | .expect('Content-Type', /bloop/) 935 | .expect('Content-Type', /html/) 936 | .end(function (err) { 937 | err.message.should.equal('expected "Content-Type" matching /bloop/, ' 938 | + 'got "text/html; charset=utf-8"'); 939 | shouldIncludeStackWithThisFile(err); 940 | done(); 941 | }); 942 | }); 943 | 944 | it('should return an error if the last one fails', function (done) { 945 | const app = express(); 946 | app.get('/', function (req, res) { 947 | res.send('hey'); 948 | }); 949 | 950 | request(app) 951 | .get('/') 952 | .expect('Content-Type', /text/) 953 | .expect('Content-Type', /html/) 954 | .expect('Content-Type', /bloop/) 955 | .end(function (err) { 956 | err.message.should.equal('expected "Content-Type" matching /bloop/, ' 957 | + 'got "text/html; charset=utf-8"'); 958 | shouldIncludeStackWithThisFile(err); 959 | done(); 960 | }); 961 | }); 962 | }); 963 | }); 964 | }); 965 | 966 | describe('request.agent(app)', function () { 967 | const app = express(); 968 | const agent = request.agent(app) 969 | .set('header', 'hey'); 970 | 971 | app.use(cookieParser()); 972 | 973 | app.get('/', function (req, res) { 974 | res.cookie('cookie', 'hey'); 975 | res.send(); 976 | }); 977 | 978 | app.get('/return_cookies', function (req, res) { 979 | if (req.cookies.cookie) res.send(req.cookies.cookie); 980 | else res.send(':('); 981 | }); 982 | 983 | app.get('/return_headers', function (req, res) { 984 | if (req.get('header')) res.send(req.get('header')); 985 | else res.send(':('); 986 | }); 987 | 988 | it('should save cookies', function (done) { 989 | agent 990 | .get('/') 991 | .expect('set-cookie', 'cookie=hey; Path=/', done); 992 | }); 993 | 994 | it('should send cookies', function (done) { 995 | agent 996 | .get('/return_cookies') 997 | .expect('hey', done); 998 | }); 999 | 1000 | it('should send global agent headers', function (done) { 1001 | agent 1002 | .get('/return_headers') 1003 | .expect('hey', done); 1004 | }); 1005 | }); 1006 | 1007 | describe('agent.host(host)', function () { 1008 | it('should set request hostname', function (done) { 1009 | const app = express(); 1010 | const agent = request.agent(app); 1011 | 1012 | app.get('/', function (req, res) { 1013 | res.send({ hostname: req.hostname }); 1014 | }); 1015 | 1016 | agent 1017 | .host('something.test') 1018 | .get('/') 1019 | .end(function (err, res) { 1020 | if (err) return done(err); 1021 | res.body.hostname.should.equal('something.test'); 1022 | done(); 1023 | }); 1024 | }); 1025 | }); 1026 | 1027 | describe('. works as expected', function () { 1028 | it('.delete should work', function (done) { 1029 | const app = express(); 1030 | app.delete('/', function (req, res) { 1031 | res.sendStatus(200); 1032 | }); 1033 | 1034 | request(app) 1035 | .delete('/') 1036 | .expect(200, done); 1037 | }); 1038 | it('.del should work', function (done) { 1039 | const app = express(); 1040 | app.delete('/', function (req, res) { 1041 | res.sendStatus(200); 1042 | }); 1043 | 1044 | request(app) 1045 | .del('/') 1046 | .expect(200, done); 1047 | }); 1048 | it('.get should work', function (done) { 1049 | const app = express(); 1050 | app.get('/', function (req, res) { 1051 | res.sendStatus(200); 1052 | }); 1053 | 1054 | request(app) 1055 | .get('/') 1056 | .expect(200, done); 1057 | }); 1058 | it('.post should work', function (done) { 1059 | const app = express(); 1060 | app.post('/', function (req, res) { 1061 | res.sendStatus(200); 1062 | }); 1063 | 1064 | request(app) 1065 | .post('/') 1066 | .expect(200, done); 1067 | }); 1068 | it('.put should work', function (done) { 1069 | const app = express(); 1070 | app.put('/', function (req, res) { 1071 | res.sendStatus(200); 1072 | }); 1073 | 1074 | request(app) 1075 | .put('/') 1076 | .expect(200, done); 1077 | }); 1078 | it('.head should work', function (done) { 1079 | const app = express(); 1080 | app.head('/', function (req, res) { 1081 | res.statusCode = 200; 1082 | res.set('Content-Encoding', 'gzip'); 1083 | res.set('Content-Length', '1024'); 1084 | res.status(200); 1085 | res.end(); 1086 | }); 1087 | 1088 | request(app) 1089 | .head('/') 1090 | .set('accept-encoding', 'gzip, deflate') 1091 | .end(function (err, res) { 1092 | if (err) return done(err); 1093 | res.should.have.property('statusCode', 200); 1094 | res.headers.should.have.property('content-length', '1024'); 1095 | done(); 1096 | }); 1097 | }); 1098 | }); 1099 | 1100 | describe('assert ordering by call order', function () { 1101 | it('should assert the body before status', function (done) { 1102 | const app = express(); 1103 | 1104 | app.set('json spaces', 0); 1105 | 1106 | app.get('/', function (req, res) { 1107 | res.status(500).json({ message: 'something went wrong' }); 1108 | }); 1109 | 1110 | request(app) 1111 | .get('/') 1112 | .expect('hey') 1113 | .expect(200) 1114 | .end(function (err, res) { 1115 | err.message.should.equal('expected \'hey\' response body, ' 1116 | + 'got \'{"message":"something went wrong"}\''); 1117 | shouldIncludeStackWithThisFile(err); 1118 | done(); 1119 | }); 1120 | }); 1121 | 1122 | it('should assert the status before body', function (done) { 1123 | const app = express(); 1124 | 1125 | app.set('json spaces', 0); 1126 | 1127 | app.get('/', function (req, res) { 1128 | res.status(500).json({ message: 'something went wrong' }); 1129 | }); 1130 | 1131 | request(app) 1132 | .get('/') 1133 | .expect(200) 1134 | .expect('hey') 1135 | .end(function (err, res) { 1136 | err.message.should.equal('expected 200 "OK", got 500 "Internal Server Error"'); 1137 | shouldIncludeStackWithThisFile(err); 1138 | done(); 1139 | }); 1140 | }); 1141 | 1142 | it('should assert the fields before body and status', function (done) { 1143 | const app = express(); 1144 | 1145 | app.set('json spaces', 0); 1146 | 1147 | app.get('/', function (req, res) { 1148 | res.status(200).json({ hello: 'world' }); 1149 | }); 1150 | 1151 | request(app) 1152 | .get('/') 1153 | .expect('content-type', /html/) 1154 | .expect('hello') 1155 | .end(function (err, res) { 1156 | err.message.should.equal('expected "content-type" matching /html/, ' 1157 | + 'got "application/json; charset=utf-8"'); 1158 | shouldIncludeStackWithThisFile(err); 1159 | done(); 1160 | }); 1161 | }); 1162 | 1163 | it('should call the expect function in order', function (done) { 1164 | const app = express(); 1165 | 1166 | app.get('/', function (req, res) { 1167 | res.status(200).json({}); 1168 | }); 1169 | 1170 | request(app) 1171 | .get('/') 1172 | .expect(function (res) { 1173 | res.body.first = 1; 1174 | }) 1175 | .expect(function (res) { 1176 | (res.body.first === 1).should.be.true; 1177 | res.body.second = 2; 1178 | }) 1179 | .end(function (err, res) { 1180 | if (err) return done(err); 1181 | (res.body.first === 1).should.be.true; 1182 | (res.body.second === 2).should.be.true; 1183 | done(); 1184 | }); 1185 | }); 1186 | 1187 | it('should call expect(fn) and expect(status, fn) in order', function (done) { 1188 | const app = express(); 1189 | 1190 | app.get('/', function (req, res) { 1191 | res.status(200).json({}); 1192 | }); 1193 | 1194 | request(app) 1195 | .get('/') 1196 | .expect(function (res) { 1197 | res.body.first = 1; 1198 | }) 1199 | .expect(200, function (err, res) { 1200 | (err === null).should.be.true; 1201 | (res.body.first === 1).should.be.true; 1202 | done(); 1203 | }); 1204 | }); 1205 | 1206 | it('should call expect(fn) and expect(header,value) in order', function (done) { 1207 | const app = express(); 1208 | 1209 | app.get('/', function (req, res) { 1210 | res 1211 | .set('X-Some-Header', 'Some value') 1212 | .send(); 1213 | }); 1214 | 1215 | request(app) 1216 | .get('/') 1217 | .expect('X-Some-Header', 'Some value') 1218 | .expect(function (res) { 1219 | res.headers['x-some-header'] = ''; 1220 | }) 1221 | .expect('X-Some-Header', '') 1222 | .end(done); 1223 | }); 1224 | 1225 | it('should call expect(fn) and expect(body) in order', function (done) { 1226 | const app = express(); 1227 | 1228 | app.get('/', function (req, res) { 1229 | res.json({ somebody: 'some body value' }); 1230 | }); 1231 | 1232 | request(app) 1233 | .get('/') 1234 | .expect(/some body value/) 1235 | .expect(function (res) { 1236 | res.body.somebody = 'nobody'; 1237 | }) 1238 | .expect(/some body value/) // res.text should not be modified. 1239 | .expect({ somebody: 'nobody' }) 1240 | .expect(function (res) { 1241 | res.text = 'gone'; 1242 | }) 1243 | .expect('gone') 1244 | .expect(/gone/) 1245 | .expect({ somebody: 'nobody' }) // res.body should not be modified 1246 | .expect('gone', done); 1247 | }); 1248 | }); 1249 | 1250 | describe('request.get(url).query(vals) works as expected', function () { 1251 | it('normal single query string value works', function (done) { 1252 | const app = express(); 1253 | app.get('/', function (req, res) { 1254 | res.status(200).send(req.query.val); 1255 | }); 1256 | 1257 | request(app) 1258 | .get('/') 1259 | .query({ val: 'Test1' }) 1260 | .expect(200, function (err, res) { 1261 | res.text.should.be.equal('Test1'); 1262 | done(); 1263 | }); 1264 | }); 1265 | 1266 | it('array query string value works', function (done) { 1267 | const app = express(); 1268 | app.get('/', function (req, res) { 1269 | res.status(200).send(Array.isArray(req.query.val)); 1270 | }); 1271 | 1272 | request(app) 1273 | .get('/') 1274 | .query({ 'val[]': ['Test1', 'Test2'] }) 1275 | .expect(200, function (err, res) { 1276 | res.req.path.should.be.equal('/?val%5B%5D=Test1&val%5B%5D=Test2'); 1277 | res.text.should.be.equal('true'); 1278 | done(); 1279 | }); 1280 | }); 1281 | 1282 | it('array query string value work even with single value', function (done) { 1283 | const app = express(); 1284 | app.get('/', function (req, res) { 1285 | res.status(200).send(Array.isArray(req.query.val)); 1286 | }); 1287 | 1288 | request(app) 1289 | .get('/') 1290 | .query({ 'val[]': ['Test1'] }) 1291 | .expect(200, function (err, res) { 1292 | res.req.path.should.be.equal('/?val%5B%5D=Test1'); 1293 | res.text.should.be.equal('true'); 1294 | done(); 1295 | }); 1296 | }); 1297 | 1298 | it('object query string value works', function (done) { 1299 | const app = express(); 1300 | app.get('/', function (req, res) { 1301 | res.status(200).send(req.query.val.test); 1302 | }); 1303 | 1304 | request(app) 1305 | .get('/') 1306 | .query({ val: { test: 'Test1' } }) 1307 | .expect(200, function (err, res) { 1308 | res.text.should.be.equal('Test1'); 1309 | done(); 1310 | }); 1311 | }); 1312 | 1313 | it('handles unknown errors (err without res)', function (done) { 1314 | const app = express(); 1315 | 1316 | nock.disableNetConnect(); 1317 | 1318 | app.get('/', function (req, res) { 1319 | res.status(200).send('OK'); 1320 | }); 1321 | 1322 | request(app) 1323 | .get('/') 1324 | // This expect should never get called, but exposes this issue with other 1325 | // errors being obscured by the response assertions 1326 | // https://github.com/ladjs/supertest/issues/352 1327 | .expect(200) 1328 | .end(function (err, res) { 1329 | should.exist(err); 1330 | should.not.exist(res); 1331 | err.should.be.an.instanceof(Error); 1332 | err.message.should.match(/Nock: Disallowed net connect/); 1333 | shouldIncludeStackWithThisFile(err); 1334 | done(); 1335 | }); 1336 | 1337 | nock.restore(); 1338 | }); 1339 | 1340 | // this scenario should never happen 1341 | // there shouldn't be any res if there is an err 1342 | // meant for test coverage for lib/test.js#169 1343 | // https://github.com/ladjs/supertest/blob/5543d674cf9aa4547927ba6010d31d9474950dec/lib/test.js#L169 1344 | it('handles unknown errors (err with res)', function (done) { 1345 | const app = express(); 1346 | 1347 | app.get('/', function (req, res) { 1348 | res.status(200).send('OK'); 1349 | }); 1350 | 1351 | const resError = new Error(); 1352 | resError.status = 400; 1353 | 1354 | const serverRes = { status: 200 }; 1355 | 1356 | request(app) 1357 | .get('/') 1358 | // private api 1359 | .assert(resError, serverRes, function (err, res) { 1360 | should.exist(err); 1361 | should.exist(res); 1362 | err.should.equal(resError); 1363 | res.should.equal(serverRes); 1364 | // close the server explicitly (as we are not using expect/end/then) 1365 | this.end(done); 1366 | }); 1367 | }); 1368 | 1369 | it('should assert using promises', function (done) { 1370 | const app = express(); 1371 | 1372 | app.get('/', function (req, res) { 1373 | res.status(400).send({ promise: true }); 1374 | }); 1375 | 1376 | request(app) 1377 | .get('/') 1378 | .expect(400) 1379 | .then((res) => { 1380 | res.body.promise.should.be.equal(true); 1381 | done(); 1382 | }); 1383 | }); 1384 | }); 1385 | 1386 | const describeHttp2 = (http2) ? describe : describe.skip; 1387 | describeHttp2('http2', function() { 1388 | // eslint-disable-next-line global-require 1389 | const proxyquire = require('proxyquire'); 1390 | 1391 | const tests = [ 1392 | { 1393 | title: 'request(app)', 1394 | api: request, 1395 | mockApi: proxyquire('../index.js', { http2: null }) 1396 | }, 1397 | { 1398 | title: 'request.agent(app)', 1399 | api: request.agent, 1400 | mockApi: proxyquire('../lib/agent.js', { http2: null }) 1401 | } 1402 | ]; 1403 | 1404 | tests.forEach(({ title, api, mockApi }) => { 1405 | describe(title, function () { 1406 | const app = function(req, res) { 1407 | res.end('hey'); 1408 | }; 1409 | 1410 | it('should fire up the app on an ephemeral port', function (done) { 1411 | api(app, { http2: true }) 1412 | .get('/') 1413 | .end(function (err, res) { 1414 | res.status.should.equal(200); 1415 | res.text.should.equal('hey'); 1416 | done(); 1417 | }); 1418 | }); 1419 | 1420 | it('should work with an active server', function (done) { 1421 | const server = http2.createServer(app); 1422 | 1423 | server.listen(function () { 1424 | api(server) 1425 | .get('/') 1426 | .http2() 1427 | .end(function (err, res) { 1428 | res.status.should.equal(200); 1429 | res.text.should.equal('hey'); 1430 | // close the external server explictly 1431 | server.close(done); 1432 | }); 1433 | }); 1434 | }); 1435 | 1436 | it('should throw error if http2 is not supported', function() { 1437 | (function() { 1438 | mockApi(app, { http2: true }); 1439 | }).should.throw('supertest: this version of Node.js does not support http2'); 1440 | }); 1441 | }); 1442 | }); 1443 | }); 1444 | -------------------------------------------------------------------------------- /test/throwError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This method needs to reside in its own module in order to properly test stack trace handling. 5 | */ 6 | module.exports = function throwError(message) { 7 | return function() { 8 | throw new Error(message); 9 | }; 10 | }; 11 | --------------------------------------------------------------------------------