├── .eslintrc ├── .gitignore ├── .travis.yml ├── history.md ├── index.js ├── mocha.opts ├── package.json ├── readme.md └── test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "parser": "babel-eslint", 7 | "rules": { 8 | "quotes": [2, "single"], 9 | "strict": [2, "global"], 10 | "no-unused-vars": 0, 11 | "no-unused-expressions": 0, 12 | "yoda": 2, 13 | "curly": 0, 14 | "no-use-before-define": 0, 15 | "no-multi-spaces": 0 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folder view configuration files 2 | .DS_Store 3 | ._* 4 | node_modules 5 | npm-debug.log 6 | index-cov.js 7 | coverage.html 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "6" 6 | after_success: 7 | - npm run coveralls 8 | -------------------------------------------------------------------------------- /history.md: -------------------------------------------------------------------------------- 1 | 0.5.2 / 2016-09-29 2 | ================== 3 | 4 | - Treat other 20x statuses (besides 200) as success [#38](https://github.com/A/superagent-mocker/pull/38) 5 | 6 | 0.5.1 / 2016-09-29 7 | ================== 8 | 9 | - Allow non object data to be handled in the request body via `send()` [#36](https://github.com/A/superagent-mocker/pull/36) 10 | 11 | 0.5.0 / 2016-09-21 12 | ================== 13 | 14 | - can now handle parallel requests [#32](https://github.com/A/superagent-mocker/issues/32) 15 | - Fix error parameter returned by mocked superagent to match the documented superagent error handling [#34](https://github.com/A/superagent-mocker/issues/34) 16 | - Support capturing query params [#35](https://github.com/A/superagent-mocker/issues/35) 17 | 18 | 0.4.0 / 2016-04-11 19 | ================== 20 | 21 | - mockable status codes added 22 | - fixed issue with sending multiple requests to single router [#28](https://github.com/A/superagent-mocker/issues/28) 23 | 24 | 25 | 0.3.0 / 2015-10-07 26 | ================== 27 | 28 | - added `clearRoute()` to remove routes by url and http-method [#19](https://github.com/shuvalov-anton/superagent-mocker/issues/19) 29 | - added `unmock` method to disable mocker [#19](https://github.com/shuvalov-anton/superagent-mocker/issues/19) 30 | - fixed missed calling context [#19](https://github.com/shuvalov-anton/superagent-mocker/issues/19) 31 | 32 | 33 | 0.2.0 / 2015-10-07 34 | ================== 35 | 36 | - added ability to set custom timeout for requests [#8](https://github.com/shuvalov-anton/superagent-mocker/issues/8) 37 | - added a method to clear all registered handlers [#10](https://github.com/shuvalov-anton/superagent-mocker/issues/10) 38 | - errors in mocks now throws into callbacks [#12](https://github.com/shuvalov-anton/superagent-mocker/issues/10) 39 | - added support for headers and send method [#11](https://github.com/shuvalov-anton/superagent-mocker/issues/11) 40 | - tests improved 41 | 42 | 0.1.6 / 2015-06-14 43 | ================== 44 | 45 | - fix: unmocked routes now works properly 46 | - fix: avoid patching when superagent is patched already 47 | - add history.md 48 | 49 | 0.1.5 / 2015-05-20 50 | ================== 51 | 52 | - fix race conditions that messed up responses 53 | 54 | 0.1.4 / 2015-05-18 55 | ================== 56 | 57 | - update kewords and readme 58 | 59 | 0.1.3 / 2015-05-18 60 | ================== 61 | 62 | - make mocker async 63 | 64 | 0.1.2 / 2015-05-13 65 | ================== 66 | 67 | - fix error with route hadn't mocked 68 | 69 | 0.1.1 / 2015-05-13 70 | ================== 71 | 72 | - fix matching 73 | - fix docs 74 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | var pathtoRegexp = require('path-to-regexp'); 7 | 8 | /** 9 | * Expose public API 10 | */ 11 | module.exports = mock; 12 | mock.get = defineRoute.bind(null, 'GET'); 13 | mock.post = defineRoute.bind(null, 'POST'); 14 | mock.put = defineRoute.bind(null, 'PUT'); 15 | mock.del = defineRoute.bind(null, 'DELETE'); 16 | mock.patch = defineRoute.bind(null, 'PATCH'); 17 | 18 | /** 19 | * Request timeout 20 | * @type {number|function} 21 | */ 22 | mock.timeout = 0; 23 | 24 | /** 25 | * List of registred routes 26 | */ 27 | var routes = []; 28 | 29 | /** 30 | * Original superagent methods 31 | * @type {{}} 32 | */ 33 | var originalMethods = {}; 34 | 35 | /** 36 | * Unregister all routes 37 | */ 38 | mock.clearRoutes = function() { 39 | routes.splice(0, routes.length) 40 | }; 41 | 42 | /** 43 | * Map api method to http method 44 | */ 45 | var methodsMapping = { 46 | get: 'GET', 47 | post: 'POST', 48 | put: 'PUT', 49 | del: 'DELETE', 50 | patch: 'PATCH' 51 | }; 52 | 53 | /** 54 | * Unregister specific route 55 | */ 56 | mock.clearRoute = function(method, url) { 57 | method = methodsMapping[method] || method; 58 | routes = routes.filter(function(route) { 59 | return !(route.url === url && route.method === method); 60 | }); 61 | }; 62 | 63 | /** 64 | * Mock 65 | */ 66 | function mock(superagent) { 67 | 68 | // don't patch if superagent was patched already 69 | if (superagent._patchedBySuperagentMocker) return mock; 70 | superagent._patchedBySuperagentMocker = true; 71 | 72 | // patch api methods (http) 73 | for (var method in methodsMapping) { 74 | if (methodsMapping.hasOwnProperty(method)) { 75 | var httpMethod = methodsMapping[method]; 76 | patch(superagent, method, httpMethod); 77 | } 78 | } 79 | 80 | var reqProto = superagent.Request.prototype; 81 | 82 | // Patch Request.end() 83 | var oldEnd = originalMethods.end = superagent.Request.prototype.end; 84 | reqProto.end = function(cb) { 85 | var state = this._superagentMockerState; 86 | if (state && state.current) { 87 | var current = state.current; 88 | setTimeout(function(request) { 89 | var error = null; 90 | var response = null; 91 | try { 92 | response = current(request); 93 | if (!/20[0-6]/.test(response.status)) { 94 | // superagent puts status and response on the error it returns, 95 | // which should be an actual instance of Error 96 | // See http://visionmedia.github.io/superagent/#error-handling 97 | error = new Error(response.status); 98 | error.status = response.status; 99 | error.response = response; 100 | response = null 101 | } else { 102 | error = null 103 | } 104 | } catch (ex) { 105 | error = ex 106 | response = null 107 | } 108 | 109 | cb && cb(error, response); 110 | }, value(mock.timeout), state.request); 111 | } else { 112 | oldEnd.call(this, cb); 113 | } 114 | }; 115 | 116 | // Patch Request.set() 117 | var oldSet = originalMethods.set = reqProto.set; 118 | reqProto.set = function(key, val) { 119 | var state = this._superagentMockerState; 120 | if (!state || !state.current) { 121 | return oldSet.call(this, key, val); 122 | } 123 | // Recursively set keys if passed an object 124 | if (isObject(key)) { 125 | for (var field in key) { 126 | this.set(field, key[field]); 127 | } 128 | return this; 129 | } 130 | if (typeof key !== 'string') { 131 | throw new TypeError('Header keys must be strings.'); 132 | } 133 | state.request.headers[key.toLowerCase()] = val; 134 | return this; 135 | }; 136 | 137 | // Patch Request.send() 138 | var oldSend = originalMethods.send = reqProto.send; 139 | reqProto.send = function(data) { 140 | var state = this._superagentMockerState; 141 | if (!state || !state.current) { 142 | return oldSend.call(this, data); 143 | } 144 | if (isObject(data)) { 145 | state.request.body = mergeObjects(state.current.body, data); 146 | } 147 | else { 148 | state.request.body = data; 149 | } 150 | return this; 151 | }; 152 | 153 | // Patch Request.query() 154 | var oldQuery = originalMethods.query = reqProto.query; 155 | reqProto.query = function(objectOrString) { 156 | var state = this._superagentMockerState; 157 | if (!state || !state.current) { 158 | return oldQuery.call(this, objectOrString); 159 | } 160 | var obj = {}; 161 | if (isString(objectOrString)) { 162 | obj = parseQueryString(objectOrString); 163 | } 164 | else if (isObject(objectOrString)) { 165 | obj = stringifyValues(objectOrString); 166 | } 167 | state.request.query = mergeObjects(state.request.query, obj); 168 | return this; 169 | } 170 | 171 | return mock; // chaining 172 | 173 | } 174 | 175 | mock.unmock = function(superagent) { 176 | ['get', 'post', 'put', 'patch', 'del'].forEach(function(method) { 177 | superagent[method] = originalMethods[method]; 178 | }); 179 | 180 | var reqProto = superagent.Request.prototype; 181 | 182 | ['end', 'set', 'send'].forEach(function(method) { 183 | reqProto[method] = originalMethods[method]; 184 | }); 185 | 186 | delete superagent._patchedBySuperagentMocker; 187 | }; 188 | 189 | /** 190 | * find route that matched with url and method 191 | * TODO: Remove data 192 | */ 193 | function match(method, url, data) { 194 | return routes.reduce(function(memo, cb) { 195 | var m = cb.match(method, url, data); 196 | return m ? m : memo; 197 | }, null); 198 | } 199 | 200 | /** 201 | * Register url and callback for `get` 202 | */ 203 | function defineRoute(method, url, handler) { 204 | routes.push(new Route({ 205 | url: url, 206 | handler: handler, 207 | method: method 208 | })); 209 | return mock; 210 | } 211 | 212 | /** 213 | * Patch superagent method 214 | */ 215 | function patch(superagent, prop, method) { 216 | var old = originalMethods[prop] = superagent[prop]; 217 | superagent[prop] = function (url, data, fn) { 218 | var current = match(method, url, data); 219 | var orig = old.call(this, url, data, fn); 220 | orig._superagentMockerState = { 221 | current: current, 222 | request: { 223 | headers: {}, 224 | body: {}, 225 | query: {} 226 | }, 227 | }; 228 | return orig; 229 | }; 230 | } 231 | 232 | /** 233 | * Route with given url 234 | */ 235 | var Route = function Route(state) { 236 | this.url = state.url; 237 | this.handler = state.handler; 238 | this.method = state.method; 239 | this.regexp = pathtoRegexp(this.url, this.keys = []); 240 | }; 241 | 242 | /** 243 | * Match route with given url 244 | */ 245 | Route.prototype.match = function(method, url, body) { 246 | if (this.method !== method) return false; 247 | var params = {}; 248 | var m = this.regexp.exec(url); 249 | if (!m) return false; 250 | for (var i = 1, len = m.length; i < len; ++i) { 251 | var key = this.keys[i - 1]; 252 | var val = m[i]; 253 | if (val !== undefined || !(hasOwnProperty.call(params, key.name))) { 254 | params[key.name] = val; 255 | } 256 | } 257 | var route = this; 258 | return function(req) { 259 | var handlerValue = route.handler({ 260 | url: url, 261 | params: params || {}, 262 | body: isObject(req.body) ? mergeObjects(body, req.body) : req.body, 263 | headers: req.headers, 264 | query: req.query 265 | }); 266 | return mergeObjects({ 267 | status: 200 268 | }, handlerValue); 269 | }; 270 | }; 271 | 272 | 273 | /** 274 | * Helpers 275 | */ 276 | 277 | /** 278 | * Simple object test 279 | * @param any obj Variable to test 280 | * @return bool True if variable is an object 281 | */ 282 | function isObject(obj) { 283 | return null != obj && 'object' == typeof obj; 284 | } 285 | 286 | /** 287 | * Simple string test 288 | * @param any val Variable to test 289 | * @return bool True if variable is a string 290 | */ 291 | function isString(val) { 292 | return 'string' === typeof val; 293 | } 294 | 295 | /** 296 | * Exec function and return value, or just return arg 297 | * @param {fn|any} val Value or fn to exec 298 | */ 299 | function value(val) { 300 | return 'function' === typeof val 301 | ? val() 302 | : val; 303 | } 304 | 305 | /** 306 | * Parses a query string like "foo=bar&baz=bat" into objects like 307 | * { foo: 'bar', baz: 'bat' } 308 | * @param s string 309 | */ 310 | function parseQueryString(s) { 311 | return s.split('&').reduce(function (obj, param) { 312 | var parts = param.split('='); 313 | var key = parts.shift(); 314 | var val = parts.shift(); 315 | if (key && val) { 316 | obj[key] = val; 317 | } 318 | return obj; 319 | }, {}); 320 | } 321 | 322 | function stringifyValues(oldObj) { 323 | return Object.keys(oldObj).reduce(function(obj, key) { 324 | obj[key] = String(oldObj[key]); 325 | return obj; 326 | }, {}); 327 | } 328 | 329 | /** 330 | * Object.assign replacement 331 | * This will always create a new object which has all of the own 332 | * properties of all objects passed. It will ignore non-objects without error. 333 | * @param ...obj object variable number of objects to merge 334 | */ 335 | function mergeObjects() { 336 | var out = {}, 337 | p; 338 | 339 | for(var index = 0; index < arguments.length; index++) { 340 | var arg = arguments[index] 341 | if(isObject(arg)) { 342 | for(var prop in arg) { 343 | if(arg.hasOwnProperty(prop)) { 344 | out[prop] = arg[prop]; 345 | } 346 | } 347 | } 348 | } 349 | 350 | return out; 351 | } 352 | -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --colors 3 | --require should 4 | --ui bdd 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "superagent-mocker", 3 | "version": "0.5.2", 4 | "description": "Pretty simple mocks for the CRUD and REST API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "make-cov": "jscoverage index.js index-cov.js", 9 | "cov": "npm run make-cov; SM_COV=1 mocha -R html-cov > coverage.html", 10 | "coveralls": "npm run make-cov; SM_COV=1 mocha -R mocha-lcov-reporter | coveralls" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:shuvalov-anton/superagent-mocker.git" 15 | }, 16 | "keywords": [ 17 | "superagent", 18 | "crud", 19 | "mocks", 20 | "stubs", 21 | "rest", 22 | "test" 23 | ], 24 | "author": "Anton (http://shuvalov.info/)", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/shuvalov-anton/superagent-mocker/issues" 28 | }, 29 | "homepage": "https://github.com/shuvalov-anton/superagent-mocker", 30 | "devDependencies": { 31 | "coveralls": "^2.11.4", 32 | "jscoverage": "^0.6.0", 33 | "mocha": "^2.2.4", 34 | "mocha-lcov-reporter": "^1.0.0", 35 | "should": "^6.0.1", 36 | "superagent": "^1.2.0" 37 | }, 38 | "dependencies": { 39 | "path-to-regexp": "^1.1.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # superagent-mocker 2 | 3 | [![Build Status](https://travis-ci.org/A/superagent-mocker.svg?branch=master)](https://travis-ci.org/A/superagent-mocker) 4 | [![Coverage Status](https://coveralls.io/repos/github/A/superagent-mocker/badge.svg?branch=master)](https://coveralls.io/github/A/superagent-mocker?branch=master) 5 | [![npm version](https://img.shields.io/npm/v/superagent-mocker.svg)](https://www.npmjs.com/package/superagent-mocker) 6 | [![npm downloads](https://img.shields.io/npm/dm/superagent-mocker.svg)](https://www.npmjs.com/package/superagent-mocker) 7 | 8 | REST API mocker for the browsers. LOOK MA NO BACKEND! 👐 9 | 10 | Written for [superagent](https://github.com/visionmedia/superagent). 11 | 12 | ## Install 13 | 14 | ```shell 15 | npm i superagent-mocker 16 | ``` 17 | 18 | ## Usage 19 | 20 | ### Setup 21 | 22 | ```js 23 | var request = require('superagent'); 24 | var mock = require('superagent-mocker')(request); 25 | ``` 26 | 27 | ### Timeout 28 | 29 | You can provide custom timeout, that can be a function or a number. Just set 30 | `timeout` property to the `mock`: 31 | 32 | ```js 33 | var mock = require('superagent-mocker'); 34 | 35 | // set just number 36 | mock.timeout = 100; 37 | 38 | // Or function to get random 39 | mock.timeout = function () { 40 | return Math.random() * 1e4 |0; 41 | } 42 | ``` 43 | 44 | ### Get 45 | 46 | You may set headers using the `mock.set()`. To ensure header keys are not case sensitive, 47 | all keys will be transformed to lower case (see example). 48 | 49 | ```js 50 | mock.get('/topics/:id', function(req) { 51 | return { 52 | id: req.params.id, 53 | content: 'Hello World!', 54 | headers: req.headers 55 | }; 56 | }); 57 | 58 | request 59 | .get('/topics/1') 60 | .set({ 'X-Custom-Header': 'value of header' }) 61 | .end(function(err, data) { 62 | console.log(data); // { id: 1, content: 'Hello World', headers: { 'x-custom-header': 'value of header' } } 63 | }) 64 | ; 65 | ``` 66 | 67 | `mock.del()` works in a similar way. 68 | 69 | ### Post 70 | 71 | You may set the body of a `POST` request as the second parameter of `mock.post()` 72 | or in `mock.send()`. Values set in `send()` will overwrite previously set values. 73 | 74 | ```js 75 | mock.post('/topics/:id', function(req) { 76 | return { 77 | id: req.params.id, 78 | body: req.body 79 | }; 80 | }); 81 | 82 | request 83 | .post('/topics/5', { 84 | content: 'I will be overwritten', 85 | fromPost: 'Foo' 86 | }) 87 | .send({ 88 | content: 'Hello world', 89 | fromSend: 'Bar' 90 | }) 91 | .end(function(err, data) { 92 | console.log(data); // { id: 5, body: { content: 'Hello world', fromPost: 'Foo', fromSend: 'Bar' } } 93 | }) 94 | ; 95 | ``` 96 | 97 | `mock.put()`, `mock.patch()` methods works in a similar way. 98 | 99 | ### Teardown 100 | 101 | You can remove all of the route handlers by calling `mock.clearRoutes()`. This is useful when defining temporary route handlers for unit tests. 102 | 103 | ```js 104 | 105 | // Using the mocha testing framework 106 | define('My API module', function(){ 107 | 108 | beforeEach(function(){ 109 | // Guarentee each test knows exactly which routes are defined 110 | mock.clearRoutes() 111 | }) 112 | 113 | it('should GET /me', function(done){ 114 | mock.get('/me', function(){done()}) 115 | api.getMe() 116 | }) 117 | 118 | it('should POST /me', function(done){ 119 | // The GET route handler no longer exists 120 | // So there is no chance to see a false positive 121 | // if the function actually calls GET /me 122 | mock.post('/me', function(){done()}) 123 | api.saveMe() 124 | }) 125 | 126 | }) 127 | ``` 128 | 129 | Or you can remove only one specified route (by method and url) 130 | 131 | ```js 132 | // to register route 133 | mock.get('/me', function(){done()}) 134 | 135 | ... 136 | 137 | // to remove registered handler 138 | mock.clearRoute('get', '/me'); 139 | 140 | ``` 141 | 142 | ### Rollback library effect 143 | 144 | In some cases it will be useful to remove patches from superagent lib after using mocks. 145 | In this cases you can use ```mock.unmock(superagent)``` method, that will rollback all patches that ```mock(superagent)``` call make. 146 | 147 | ## License 148 | 149 | MIT © [Shuvalov Anton](http://shuvalov.info) 150 | 151 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, before, navigator */ 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * Dependencies 7 | */ 8 | var request = require('superagent'); 9 | var should = require('should'); 10 | var mock = process.env.SM_COV 11 | ? require('./index-cov')(request) 12 | : require('./index')(request); 13 | 14 | describe('superagent mock', function() { 15 | 16 | beforeEach(function() { 17 | mock.clearRoutes(); 18 | mock.timeout = 0; 19 | }); 20 | 21 | describe('API', function() { 22 | 23 | it('should mock for get', function(done) { 24 | mock.get('/topics/:id', function(req) { 25 | req.params.id.should.be.equal('1'); 26 | return { id: req.params.id }; 27 | }); 28 | request.get('/topics/1').end(function(_, data) { 29 | data.should.have.property('id', '1'); 30 | done(); 31 | }); 32 | }); 33 | 34 | it('should mock multiple requests', function(done) { 35 | mock.get('/thread/:id', function(req) { 36 | return { id: req.params.id }; 37 | }); 38 | var finished = 0; 39 | var r1 = request.get('/thread/1'); 40 | var r2 = request.get('/thread/2'); 41 | 42 | r1.end(function(_, data) { 43 | data.should.have.property('id', '1'); 44 | if (++finished == 2) done(); 45 | }); 46 | r2.end(function(_, data) { 47 | data.should.have.property('id', '2'); 48 | if (++finished == 2) done(); 49 | }); 50 | }); 51 | 52 | it('should mock for post', function(done) { 53 | mock.post('/topics/:id', function(req) { 54 | return { 55 | id: req.params.id, 56 | content: req.body.content 57 | }; 58 | }); 59 | request 60 | .post('/topics/5', { content: 'Hello world' }) 61 | .end(function(_, data) { 62 | data.should.have.property('id', '5'); 63 | data.should.have.property('content', 'Hello world'); 64 | done(); 65 | }); 66 | }); 67 | 68 | it('should mock for put', function(done) { 69 | mock.put('/topics/:id', function(req) { 70 | return { id: req.params.id, content: req.body.content }; 71 | }); 72 | request 73 | .put('/topics/7', { id: 7, content: 'hello world!11' }) 74 | .end(function(_, data) { 75 | data.should.have.property('id', '7'); 76 | data.should.have.property('content', 'hello world!11'); 77 | done(); 78 | }); 79 | }); 80 | 81 | it('should mock for patch', function(done) { 82 | mock.patch('/topics/:id', function(req) { 83 | return { id: req.params.id, content: req.body.content }; 84 | }); 85 | request 86 | .patch('/topics/7', { id: 7, content: 'hello world!11' }) 87 | .end(function(_, data) { 88 | data.should.have.property('id', '7'); 89 | data.should.have.property('content', 'hello world!11'); 90 | done(); 91 | }); 92 | }); 93 | 94 | it('should mock for delete', function(done) { 95 | mock.del('/topics/:id', function(req) { 96 | return { id: req.params.id, content: req.body.content }; 97 | }); 98 | request 99 | .del('/topics/7', { id: 7, content: 'yay' }) 100 | .end(function(_, data) { 101 | data.should.have.property('id', '7'); 102 | data.should.have.property('content', 'yay'); 103 | done(); // just done 104 | }); 105 | }); 106 | 107 | it('should be async', function(done) { 108 | var isAsync = true; 109 | mock.get('/async', function(req) { 110 | isAsync = false; 111 | }); 112 | request 113 | .get('/async') 114 | .end(); 115 | isAsync.should.be.true; 116 | done(); 117 | }); 118 | 119 | it('should work correct with unmocked requests', function(done) { 120 | request 121 | .get('http://example.com') 122 | .query({ foo: 'bar' }) 123 | .end(function(err, res) { 124 | done(err); 125 | }); 126 | }); 127 | 128 | it('should work with custom timeout', function(done) { 129 | var startedAt = +new Date(); 130 | mock.timeout = 100; 131 | mock.get('/timeout', noop); 132 | request 133 | .get('/timeout') 134 | .end(function(err, res) { 135 | var finishedAt = +new Date(); 136 | var offset = finishedAt - startedAt; 137 | offset.should.be.above(mock.timeout - 1); 138 | done(err); 139 | }); 140 | }); 141 | 142 | it('should work with custom timeout function', function(done) { 143 | var startedAt = +new Date(); 144 | mock.get('/timeout', noop); 145 | mock.timeout = function () { return 200; }; 146 | request 147 | .get('/timeout') 148 | .end(function(err, res) { 149 | var finishedAt = +new Date(); 150 | var offset = finishedAt - startedAt; 151 | offset.should.be.above(199); 152 | done(err); 153 | }); 154 | }); 155 | 156 | it('should clear registered routes', function(done) { 157 | mock.get('/topics', noop); 158 | mock.clearRoutes(); 159 | request 160 | .get('/topics') 161 | .end(function(err, res) { 162 | should.throws(function() { 163 | should.ifError(err); 164 | }, /ECONNREFUSED/); 165 | done(); 166 | }); 167 | }); 168 | 169 | it('should clear registered specific route', function(done) { 170 | mock 171 | .get('/topics', noop) 172 | .get('/posters', function() { 173 | return { id: 7 }; 174 | }); 175 | mock.clearRoute('get', '/topics'); 176 | request 177 | .get('/topics') 178 | .end(function(err, res) { 179 | should.throws(function() { 180 | should.ifError(err); 181 | }, /ECONNREFUSED/); 182 | 183 | request 184 | .get('/posters') 185 | .end(function(_, data) { 186 | data.should.have.property('id', 7); 187 | done(); 188 | }); 189 | }); 190 | }); 191 | 192 | it('should provide error when method throws', function(done) { 193 | var error = Error('This should be in the callback!'); 194 | mock.get('http://example.com', function(req) { 195 | throw error; 196 | }); 197 | request 198 | .get('http://example.com') 199 | .end(function(err, res) { 200 | err.should.equal(error); 201 | done(); 202 | }); 203 | }); 204 | 205 | it('should not treat a 204 as an error', function(done) { 206 | mock.get('/topics/:id', function(req) { 207 | return {status: 204}; 208 | }); 209 | request.get('/topics/1') 210 | .end(function(err, data) { 211 | should.not.exist(err); 212 | data.should.have.property('status', 204); 213 | done(); 214 | }); 215 | }); 216 | 217 | it('should support status code in response', function(done) { 218 | mock.get('/topics/:id', function(req) { 219 | return {body: {}, status: 500}; 220 | }); 221 | request.get('/topics/1') 222 | .end(function(err, data) { 223 | err.should.have.property('status', 500); 224 | err.should.have.property('response'); 225 | should.deepEqual(err.response, {body: {}, status: 500}); 226 | done(); 227 | }); 228 | }); 229 | 230 | it('should support headers', function(done) { 231 | mock.get('/topics/:id', function(req) { 232 | return req.headers; 233 | }); 234 | request.get('/topics/1') 235 | .set('My-Header', 'my-Value') 236 | .set('User-Agent', 'Opera Mini') 237 | .end(function(_, data) { 238 | // lowercase 239 | data.should.have.property('my-header', 'my-Value') 240 | data.should.have.property('user-agent', 'Opera Mini') 241 | done(); 242 | }); 243 | }); 244 | 245 | it('should support multiple headers', function(done) { 246 | mock.get('/', function(req) { 247 | return req.headers; 248 | }); 249 | request.get('/') 250 | .set({ 251 | 'My-Header': 'my-Value', 252 | 'User-Agent': 'Opera Mini', 253 | }) 254 | .end(function(_, data) { 255 | data.should.have.property('my-header', 'my-Value') 256 | data.should.have.property('user-agent', 'Opera Mini') 257 | done(); 258 | }) 259 | }) 260 | 261 | it('should throw error when header isn\'t a string', function() { 262 | mock.get('/topics/:id', function(req) { 263 | return req.headers; 264 | }); 265 | should.throws(function() { 266 | request.get('/topics/1') 267 | .set(42, 'my-Value') 268 | .end(function(_, data) { 269 | done(); 270 | }); 271 | should.ifError(err); 272 | }, /Header keys must be strings/); 273 | }); 274 | 275 | it('should pass data from send()', function(done) { 276 | mock.post('/topics/:id', function(req) { 277 | return req.body; 278 | }); 279 | request 280 | .post('/topics/5') 281 | .send({ content: 'Hello world' }) 282 | .end(function(_, data) { 283 | data.should.have.property('content', 'Hello world'); 284 | done(); 285 | }) 286 | ; 287 | }); 288 | 289 | it('should pass non-object data from send()', function(done) { 290 | mock.post('/topics/:id', function(req) { 291 | return { body: req.body }; 292 | }); 293 | request 294 | .post('/topics/6') 295 | .send('foo bar baz') 296 | .end(function(_, data) { 297 | should.equal(data.body, 'foo bar baz'); 298 | done(); 299 | }) 300 | ; 301 | }); 302 | 303 | it('should rewrite post() data by send()', function(done) { 304 | mock.post('/topics/:id', function(req) { 305 | return req.body; 306 | }); 307 | request 308 | .post('/topics/5', { content: 'Hello Universe' }) 309 | .send({ content: 'Hello world', title: 'Yay!' }) 310 | .end(function(_, data) { 311 | data.should.have.property('title', 'Yay!'); 312 | data.should.have.property('content', 'Hello world'); 313 | done(); 314 | }) 315 | ; 316 | }); 317 | 318 | it('should parse parameters from query()', function(done) { 319 | mock.get('/topics/:id', function(req) { 320 | return req; 321 | }); 322 | request 323 | .get('/topics/5') 324 | .query('hello=world') 325 | .query('xx=yy&zz=0') 326 | .query({ test: 'yay' }) 327 | .query({ foo: 'bar', baz: 'bat' }) 328 | .end(function(_, data) { 329 | data.should.have.property('query'); 330 | should.deepEqual(data.query, { 331 | hello: 'world', 332 | xx: 'yy', 333 | zz: '0', 334 | test: 'yay', 335 | foo: 'bar', 336 | baz: 'bat' 337 | }); 338 | done(); 339 | }) 340 | ; 341 | }); 342 | 343 | it('should remove patches by unmock()', function() { 344 | mock.unmock(request); 345 | (request._patchedBySuperagentMocker === void 0).should.be.true; 346 | }); 347 | 348 | }); 349 | 350 | }); 351 | 352 | /** 353 | * Just noop 354 | */ 355 | function noop() {}; 356 | --------------------------------------------------------------------------------