fetch-mock
Advanced tools
Comparing version 3.2.1 to 4.0.0
@@ -265,4 +265,5 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fetchMock = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
this.addRoutes(config.routes); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
@@ -284,4 +285,2 @@ } | ||
* a promise of a Response, or forwading to native fetch | ||
* Has a helper method .augment(routes), which can be used to add additional | ||
* routes to the router | ||
*/ | ||
@@ -296,7 +295,7 @@ | ||
config = config || {}; | ||
var router = this.constructRouter(config); | ||
this.addRoutes(config.routes); | ||
config.greed = config.greed || 'none'; | ||
var mock = function mock(url, opts) { | ||
var response = router(url, opts); | ||
var response = _this.router(url, opts); | ||
if (response) { | ||
@@ -321,104 +320,62 @@ debug('response found for ' + url); | ||
mock.augment = function (routes) { | ||
router.augment(routes); | ||
}; | ||
return mock; | ||
} | ||
/** | ||
* constructRouter | ||
* Constructs a function which identifies if calls to fetch match any of the configured routes | ||
* and returns the Response defined by the route | ||
* @param {Object} config Can define routes and/or responses, which will be used to augment any | ||
* previously set by registerRoute() | ||
* @return {Function} Function expecting url + options or a Request object, and returning | ||
* a response config or undefined. | ||
* Has a helper method .augment(routes), which can be used to add additional | ||
* routes to the router | ||
* router | ||
* Given url + options or a Request object, checks to see if ait is matched by any routes and returns | ||
* config for a response or undefined. | ||
* @param {String|Request} url | ||
* @param {Object} | ||
* @return {Object} | ||
*/ | ||
}, { | ||
key: 'constructRouter', | ||
value: function constructRouter(config) { | ||
key: 'router', | ||
value: function router(url, opts) { | ||
var _this2 = this; | ||
debug('building router'); | ||
var response = undefined; | ||
debug('searching for matching route for ' + url); | ||
this.routes.some(function (route) { | ||
var routes = undefined; | ||
if (route.matcher(url, opts)) { | ||
debug('Found matching route (' + route.name + ') for ' + url); | ||
_this2.push(route.name, [url, opts]); | ||
if (config.routes) { | ||
(function () { | ||
debug('applying one time only routes'); | ||
if (!(config.routes instanceof Array)) { | ||
config.routes = [config.routes]; | ||
debug('Setting response for ' + route.name); | ||
response = route.response; | ||
if (typeof response === 'function') { | ||
debug('Constructing dynamic response for ' + route.name); | ||
response = response(url, opts); | ||
} | ||
var preRegisteredRoutes = {}; | ||
_this2.routes.forEach(function (route) { | ||
preRegisteredRoutes[route.name] = route; | ||
}); | ||
// Allows selective application of some of the preregistered routes | ||
routes = config.routes.map(function (route) { | ||
if (typeof route === 'string') { | ||
debug('applying preregistered route ' + route); | ||
return preRegisteredRoutes[route]; | ||
} else { | ||
debug('applying one time route ' + route.name); | ||
return compileRoute(route); | ||
} | ||
}); | ||
})(); | ||
} else if (this.routes.length) { | ||
debug('no one time only routes defined. Using preregistered routes only'); | ||
routes = [].slice.call(this.routes); | ||
} else { | ||
throw new Error('When no preconfigured routes set using .registerRoute(), .mock() must be passed configuration for routes'); | ||
} | ||
var routeNames = {}; | ||
routes.forEach(function (route) { | ||
if (routeNames[route.name]) { | ||
throw new Error('Route names must be unique'); | ||
return true; | ||
} | ||
routeNames[route.name] = true; | ||
}); | ||
config.responses = config.responses || {}; | ||
debug('returning response for ' + url); | ||
return response; | ||
} | ||
var router = function router(url, opts) { | ||
var response = undefined; | ||
debug('searching for matching route for ' + url); | ||
routes.some(function (route) { | ||
/** | ||
* addRoutes | ||
* Adds routes to those used by fetchMock to match fetch calls | ||
* @param {Object|Array} routes route configurations | ||
*/ | ||
if (route.matcher(url, opts)) { | ||
debug('Found matching route (' + route.name + ') for ' + url); | ||
_this2.push(route.name, [url, opts]); | ||
}, { | ||
key: 'addRoutes', | ||
value: function addRoutes(routes) { | ||
if (config.responses[route.name]) { | ||
debug('Overriding response for ' + route.name); | ||
response = config.responses[route.name]; | ||
} else { | ||
debug('Using default response for ' + route.name); | ||
response = route.response; | ||
} | ||
if (!routes) { | ||
throw new Error('.mock() must be passed configuration for routes'); | ||
} | ||
if (typeof response === 'function') { | ||
debug('Constructing dynamic response for ' + route.name); | ||
response = response(url, opts); | ||
} | ||
return true; | ||
} | ||
}); | ||
debug('applying one time only routes'); | ||
if (!(routes instanceof Array)) { | ||
routes = [routes]; | ||
} | ||
debug('returning response for ' + url); | ||
return response; | ||
}; | ||
router.augment = function (additionalRoutes) { | ||
routes = routes.concat(additionalRoutes.map(compileRoute)); | ||
}; | ||
return router; | ||
// Allows selective application of some of the preregistered routes | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
} | ||
@@ -430,3 +387,3 @@ | ||
* @param {String} name Name of the route matched by the call | ||
* @param {Object} call {url, opts} pair | ||
* @param {Array} call [url, opts] pair | ||
*/ | ||
@@ -458,2 +415,3 @@ | ||
this.reset(); | ||
this.routes = []; | ||
debug('fetch restored'); | ||
@@ -514,2 +472,24 @@ } | ||
} | ||
}, { | ||
key: 'lastCall', | ||
value: function lastCall(name) { | ||
var calls = name ? this.calls(name) : this.calls().matched; | ||
if (calls && calls.length) { | ||
return calls[calls.length - 1]; | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
}, { | ||
key: 'lastUrl', | ||
value: function lastUrl(name) { | ||
var call = this.lastCall(name); | ||
return call && call[0]; | ||
} | ||
}, { | ||
key: 'lastOptions', | ||
value: function lastOptions(name) { | ||
var call = this.lastCall(name); | ||
return call && call[1]; | ||
} | ||
@@ -529,62 +509,2 @@ /** | ||
} | ||
/** | ||
* registerRoute | ||
* Creates a route that will persist even when fetchMock.restore() is called | ||
* See README for details of parameters | ||
*/ | ||
}, { | ||
key: 'registerRoute', | ||
value: function registerRoute(name, matcher, response) { | ||
debug('registering routes'); | ||
var routes = undefined; | ||
if (name instanceof Array) { | ||
routes = name; | ||
} else if (arguments.length === 3) { | ||
routes = [{ | ||
name: name, | ||
matcher: matcher, | ||
response: response | ||
}]; | ||
} else { | ||
routes = [name]; | ||
} | ||
debug('registering routes: ' + routes.map(function (r) { | ||
return r.name; | ||
})); | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
} | ||
/** | ||
* unregisterRoute | ||
* Removes a persistent route | ||
* See README for details of parameters | ||
*/ | ||
}, { | ||
key: 'unregisterRoute', | ||
value: function unregisterRoute(names) { | ||
if (!names) { | ||
debug('unregistering all routes'); | ||
this.routes = []; | ||
return; | ||
} | ||
if (!(names instanceof Array)) { | ||
names = [names]; | ||
} | ||
debug('unregistering routes: ' + names); | ||
this.routes = this.routes.filter(function (route) { | ||
var keep = names.indexOf(route.name) === -1; | ||
if (!keep) { | ||
debug('unregistering route ' + route.name); | ||
} | ||
return keep; | ||
}); | ||
} | ||
}]); | ||
@@ -591,0 +511,0 @@ |
@@ -251,4 +251,5 @@ 'use strict'; | ||
this.addRoutes(config.routes); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
@@ -270,4 +271,2 @@ } | ||
* a promise of a Response, or forwading to native fetch | ||
* Has a helper method .augment(routes), which can be used to add additional | ||
* routes to the router | ||
*/ | ||
@@ -282,7 +281,7 @@ | ||
config = config || {}; | ||
var router = this.constructRouter(config); | ||
this.addRoutes(config.routes); | ||
config.greed = config.greed || 'none'; | ||
var mock = function mock(url, opts) { | ||
var response = router(url, opts); | ||
var response = _this.router(url, opts); | ||
if (response) { | ||
@@ -307,104 +306,62 @@ debug('response found for ' + url); | ||
mock.augment = function (routes) { | ||
router.augment(routes); | ||
}; | ||
return mock; | ||
} | ||
/** | ||
* constructRouter | ||
* Constructs a function which identifies if calls to fetch match any of the configured routes | ||
* and returns the Response defined by the route | ||
* @param {Object} config Can define routes and/or responses, which will be used to augment any | ||
* previously set by registerRoute() | ||
* @return {Function} Function expecting url + options or a Request object, and returning | ||
* a response config or undefined. | ||
* Has a helper method .augment(routes), which can be used to add additional | ||
* routes to the router | ||
* router | ||
* Given url + options or a Request object, checks to see if ait is matched by any routes and returns | ||
* config for a response or undefined. | ||
* @param {String|Request} url | ||
* @param {Object} | ||
* @return {Object} | ||
*/ | ||
}, { | ||
key: 'constructRouter', | ||
value: function constructRouter(config) { | ||
key: 'router', | ||
value: function router(url, opts) { | ||
var _this2 = this; | ||
debug('building router'); | ||
var response = undefined; | ||
debug('searching for matching route for ' + url); | ||
this.routes.some(function (route) { | ||
var routes = undefined; | ||
if (route.matcher(url, opts)) { | ||
debug('Found matching route (' + route.name + ') for ' + url); | ||
_this2.push(route.name, [url, opts]); | ||
if (config.routes) { | ||
(function () { | ||
debug('applying one time only routes'); | ||
if (!(config.routes instanceof Array)) { | ||
config.routes = [config.routes]; | ||
debug('Setting response for ' + route.name); | ||
response = route.response; | ||
if (typeof response === 'function') { | ||
debug('Constructing dynamic response for ' + route.name); | ||
response = response(url, opts); | ||
} | ||
var preRegisteredRoutes = {}; | ||
_this2.routes.forEach(function (route) { | ||
preRegisteredRoutes[route.name] = route; | ||
}); | ||
// Allows selective application of some of the preregistered routes | ||
routes = config.routes.map(function (route) { | ||
if (typeof route === 'string') { | ||
debug('applying preregistered route ' + route); | ||
return preRegisteredRoutes[route]; | ||
} else { | ||
debug('applying one time route ' + route.name); | ||
return compileRoute(route); | ||
} | ||
}); | ||
})(); | ||
} else if (this.routes.length) { | ||
debug('no one time only routes defined. Using preregistered routes only'); | ||
routes = [].slice.call(this.routes); | ||
} else { | ||
throw new Error('When no preconfigured routes set using .registerRoute(), .mock() must be passed configuration for routes'); | ||
} | ||
var routeNames = {}; | ||
routes.forEach(function (route) { | ||
if (routeNames[route.name]) { | ||
throw new Error('Route names must be unique'); | ||
return true; | ||
} | ||
routeNames[route.name] = true; | ||
}); | ||
config.responses = config.responses || {}; | ||
debug('returning response for ' + url); | ||
return response; | ||
} | ||
var router = function router(url, opts) { | ||
var response = undefined; | ||
debug('searching for matching route for ' + url); | ||
routes.some(function (route) { | ||
/** | ||
* addRoutes | ||
* Adds routes to those used by fetchMock to match fetch calls | ||
* @param {Object|Array} routes route configurations | ||
*/ | ||
if (route.matcher(url, opts)) { | ||
debug('Found matching route (' + route.name + ') for ' + url); | ||
_this2.push(route.name, [url, opts]); | ||
}, { | ||
key: 'addRoutes', | ||
value: function addRoutes(routes) { | ||
if (config.responses[route.name]) { | ||
debug('Overriding response for ' + route.name); | ||
response = config.responses[route.name]; | ||
} else { | ||
debug('Using default response for ' + route.name); | ||
response = route.response; | ||
} | ||
if (!routes) { | ||
throw new Error('.mock() must be passed configuration for routes'); | ||
} | ||
if (typeof response === 'function') { | ||
debug('Constructing dynamic response for ' + route.name); | ||
response = response(url, opts); | ||
} | ||
return true; | ||
} | ||
}); | ||
debug('applying one time only routes'); | ||
if (!(routes instanceof Array)) { | ||
routes = [routes]; | ||
} | ||
debug('returning response for ' + url); | ||
return response; | ||
}; | ||
router.augment = function (additionalRoutes) { | ||
routes = routes.concat(additionalRoutes.map(compileRoute)); | ||
}; | ||
return router; | ||
// Allows selective application of some of the preregistered routes | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
} | ||
@@ -416,3 +373,3 @@ | ||
* @param {String} name Name of the route matched by the call | ||
* @param {Object} call {url, opts} pair | ||
* @param {Array} call [url, opts] pair | ||
*/ | ||
@@ -444,2 +401,3 @@ | ||
this.reset(); | ||
this.routes = []; | ||
debug('fetch restored'); | ||
@@ -500,2 +458,24 @@ } | ||
} | ||
}, { | ||
key: 'lastCall', | ||
value: function lastCall(name) { | ||
var calls = name ? this.calls(name) : this.calls().matched; | ||
if (calls && calls.length) { | ||
return calls[calls.length - 1]; | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
}, { | ||
key: 'lastUrl', | ||
value: function lastUrl(name) { | ||
var call = this.lastCall(name); | ||
return call && call[0]; | ||
} | ||
}, { | ||
key: 'lastOptions', | ||
value: function lastOptions(name) { | ||
var call = this.lastCall(name); | ||
return call && call[1]; | ||
} | ||
@@ -515,62 +495,2 @@ /** | ||
} | ||
/** | ||
* registerRoute | ||
* Creates a route that will persist even when fetchMock.restore() is called | ||
* See README for details of parameters | ||
*/ | ||
}, { | ||
key: 'registerRoute', | ||
value: function registerRoute(name, matcher, response) { | ||
debug('registering routes'); | ||
var routes = undefined; | ||
if (name instanceof Array) { | ||
routes = name; | ||
} else if (arguments.length === 3) { | ||
routes = [{ | ||
name: name, | ||
matcher: matcher, | ||
response: response | ||
}]; | ||
} else { | ||
routes = [name]; | ||
} | ||
debug('registering routes: ' + routes.map(function (r) { | ||
return r.name; | ||
})); | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
} | ||
/** | ||
* unregisterRoute | ||
* Removes a persistent route | ||
* See README for details of parameters | ||
*/ | ||
}, { | ||
key: 'unregisterRoute', | ||
value: function unregisterRoute(names) { | ||
if (!names) { | ||
debug('unregistering all routes'); | ||
this.routes = []; | ||
return; | ||
} | ||
if (!(names instanceof Array)) { | ||
names = [names]; | ||
} | ||
debug('unregistering routes: ' + names); | ||
this.routes = this.routes.filter(function (route) { | ||
var keep = names.indexOf(route.name) === -1; | ||
if (!keep) { | ||
debug('unregistering route ' + route.name); | ||
} | ||
return keep; | ||
}); | ||
} | ||
}]); | ||
@@ -577,0 +497,0 @@ |
{ | ||
"name": "fetch-mock", | ||
"version": "3.2.1", | ||
"version": "4.0.0", | ||
"description": "Mock http requests made using fetch (or isomorphic-fetch)", | ||
@@ -5,0 +5,0 @@ "main": "src/server.js", |
217
README.md
# fetch-mock [![Build Status](https://travis-ci.org/wheresrhys/fetch-mock.svg?branch=master)](https://travis-ci.org/wheresrhys/fetch-mock) | ||
Mock http requests made using fetch (or [isomorphic-fetch](https://www.npmjs.com/package/isomorphic-fetch)). As well as shorthand methods for the simplest use cases, it offers a flexible API for customising mocking behaviour, and can also be persisted (with resettable state) over a series of tests. | ||
Mock http requests made using fetch (or [isomorphic-fetch](https://www.npmjs.com/package/isomorphic-fetch)). As well as shorthand methods for the simplest use cases, it offers a flexible API for customising all aspects of mocking behaviour. | ||
@@ -8,10 +8,10 @@ ## Installation and usage | ||
[Troubleshooting](#troubleshooting), [V3 changelog](https://github.com/wheresrhys/fetch-mock/pull/35) | ||
[Troubleshooting](#troubleshooting), [V4 changelog](#v4-changelog) | ||
*To output useful messages for debugging `export DEBUG=fetch-mock`* | ||
**`require('fetch-mock')`** exports a singleton with the following methods | ||
## Basic usage | ||
**`require('fetch-mock')` exports a singleton with the following methods** | ||
#### `mock(matcher, response)` or `mock(matcher, method, response)` | ||
@@ -44,8 +44,19 @@ Replaces `fetch()` with a stub which records its calls, grouped by route, and optionally returns a mocked `Response` object or passes the call through to `fetch()`. Calls to `.mock()` can be chained. | ||
**For the methods below `matcher`, if given, should be either the name of a route (see advanced usage below) or equal to `matcher.toString()` for any unnamed route** | ||
#### `calls(matcher)` | ||
Returns an object `{matched: [], unmatched: []}` containing arrays of all calls to fetch, grouped by whether fetch-mock matched them or not. If `matcher` is specified and is equal to `matcher.toString()` for any of the mocked routes then only calls to fetch matching that route are returned. | ||
Returns an object `{matched: [], unmatched: []}` containing arrays of all calls to fetch, grouped by whether fetch-mock matched them or not. If `matcher` is specified then only calls to fetch matching that route are returned. | ||
#### `called(matcher)` | ||
Returns a Boolean indicating whether fetch was called and a route was matched. If `matcher` is specified and is equal to `matcher.toString()` for any of the mocked routes then only returns `true` if that particular route was matched. | ||
Returns a Boolean indicating whether fetch was called and a route was matched. If `matcher` is specified it only returns `true` if that particular route was matched. | ||
#### `lastCall(matcher)` | ||
Returns the arguments for the last matched call to fetch | ||
#### `lastUrl(matcher)` | ||
Returns the url for the last matched call to fetch | ||
#### `lastOptions(matcher)` | ||
Returns the options for the last matched call to fetch | ||
##### Example | ||
@@ -65,3 +76,4 @@ | ||
expect(fetchMock.calls().unmatched().length).to.equal(0); | ||
expect(JSON.parse(fetchMock.calls('http://domain2'[)[0][1].body)).to.deep.equal({prop: 'val'}); | ||
expect(JSON.parse(fetchMock.lastUrl('http://domain2'))).to.equal('http://domain2/endpoint'); | ||
expect(JSON.parse(fetchMock.lastOptions('http://domain2').body)).to.deep.equal({prop: 'val'}); | ||
fetchMock.restore(); | ||
@@ -76,3 +88,3 @@ }) | ||
Use a configuration object to define a route to mock. | ||
* `name` [optional]: A unique string naming the route. Used to subsequently retrieve references to the calls, grouped by name. If not specified defaults to `matcher.toString()` | ||
* `name` [optional]: A unique string naming the route. Used to subsequently retrieve references to the calls, grouped by name. If not specified defaults to `matcher.toString()` *Note: If a non-unique name is provided no error will be thrown (because names are optional, so auto-generated ones may legitimately clash)* | ||
* `method` [optional]: http method | ||
@@ -87,17 +99,4 @@ * `matcher` [required]: as specified above | ||
Pas in an object containing more complex config for fine grained control over every aspect of mocking behaviour. May have the following properties | ||
* `routes`: Either a single route config object or an array of them (see above). When routes have already been registered using `registerRoute()` they can be selectively applied by referencing their names. e.g. If routes 1 - 4 are defined then `fetchMock.mock({routes: ['route1', 'route4'])` will only attempt to match calls against routes 1 and 4, treating any calls that woudl have matched routes 2 or 3 as unmatched. The array of routes may contain a mixture of route names and route config objects. The default behaviour when routes are passed into `.mock()` is for none of the preregistered routes to be applied. | ||
* `responses`: When `registerRoute()` (see below) has already been used to register some routes then `responses` can be used to override the default response. Its value should be an object mapping route names to responses, which should be similar to those provided in the `response` property of stanadard route configurations e.g. | ||
``` | ||
responses: { | ||
session: function (url, opts) { | ||
if (opts.headers.Authorization) { | ||
return {user: 'dummy-authorized-user'}; | ||
} else { | ||
return {user: 'dummy-unauthorized-user'}; | ||
} | ||
} | ||
} | ||
``` | ||
Pass in an object containing more complex config for fine grained control over every aspect of mocking behaviour. May have the following properties | ||
* `routes`: Either a single route config object or an array of them (see above). | ||
* `greed`: Determines how the mock handles unmatched requests | ||
@@ -108,18 +107,2 @@ * 'none': all unmatched calls get passed through to `fetch()` | ||
#### `calls(routeName)` | ||
Returns an array of arrays of the arguments passed to `fetch()` that matched the given route. | ||
#### `called(routeName)` | ||
Returns a Boolean denoting whether any calls matched the given route. | ||
#### `registerRoute()` | ||
Often your application/module will need a mocked response for some http requests in order to initialise properly, even if the content of those calls are not the subject of a given test e.g. a mock response from an authentication service and a multi-variant testing service might be necessary in order to test the UI for a version of a log in form. It's helpful to be able to define some default responses for these services which will exist throughout all or a large subset of your tests. `registerRoute()` aims to fulfil this need. All these predefined routes can be overridden when `mock(config)` is called. | ||
* `registerRoute(object)`: An object similar to the route objects accepted by `mock()` | ||
* `registerRoute(array)`: An array of the above objects | ||
* `registerRoute(name, matcher, response)`: The 3 properties of the route object spread across 3 parameters | ||
#### `unregisterRoute(name)` | ||
Unregisters one or more previously registered routes. Accepts either a string or an array of strings | ||
#### `useNonGlobalFetch(func)` | ||
@@ -151,3 +134,2 @@ When using isomorphic-fetch or node-fetch ideally `fetch` should be added as a global. If not possible to do so you can still use fetch-mock in combination with [mockery](https://github.com/mfncooper/mockery) in nodejs. To use fetch-mock with with [mockery](https://github.com/mfncooper/mockery) you will need to use this function to prevent fetch-mock trying to mock the function globally. | ||
### Environment doesn't support requiring fetch-mock? | ||
* If your client-side code or tests do not use a loader that respects the browser field of package.json use `require('fetch-mock/es5/client')`. | ||
@@ -161,141 +143,22 @@ * If you need to use fetch-mock without commonjs, you can include the precompiled `node_modules/fetch-mock/es5/client-browserified.js` in a script tag. This loads fetch-mock into the `fetchMock` global variable. | ||
## Examples | ||
```javascript | ||
## V4 changelog | ||
* `registerRoute()` and `unregisterRoute()` have been removed to simplify the API. Since V3, calls to `.mock()` can be chained, so persisting routes over a series of tests can easily be achieved by means of a beforeEach or helper e.g. | ||
``` | ||
beforeEach(() => { | ||
fetchMock | ||
.mock('http://auth.service.com/user', 200) | ||
.mock('http://mvt.service.com/session', {test1: true, test2: false}) | ||
}); | ||
var fetchMock = require('fetch-mock'); | ||
// Simplest use case | ||
it('should pretend to be Rambo', done => { | ||
fetchMock.mock('http://rambo.was.ere', 301); | ||
fetch('http://rambo.was.ere') | ||
.then(res => { | ||
expect(fetchMock.calls().matched.length).to.equal(1); | ||
expect(res.status).to.equal(301); | ||
fetchMock.restore(); | ||
done(); | ||
}); | ||
}) | ||
// Optionally set up some routes you will always want to mock | ||
// Accepts an array of config objects or three parameters, | ||
// name, matcher and response, to add a single route | ||
fetchMock.registerRoute([ | ||
{ | ||
name: 'session', | ||
matcher: 'https://sessionservice.host.com', | ||
response: { | ||
body: 'user-12345', | ||
// opts is as expected by https://github.com/bitinn/node-fetch/blob/master/lib/response.js | ||
// headers should be passed as an object literal (fetch-mock will convert it into a Headers instance) | ||
// status defaults to 200 | ||
opts: { | ||
headers: { | ||
'x-status': 'unsubscribed' | ||
}, | ||
status: 401 | ||
} | ||
} | ||
}, | ||
{ | ||
name: 'geo', | ||
matcher: /^https\:\/\/geoservice\.host\.com/, | ||
// objects will be converted to strings using JSON.stringify before being returned | ||
response: { | ||
body: { | ||
country: 'uk' | ||
} | ||
} | ||
} | ||
]) | ||
it('should do A', function () { | ||
fetchMock.mock({ | ||
// none: all unmatched calls get sent straight through to the default fetch | ||
// bad: all unmatched calls result in a rejected promise | ||
// good: all unmatched calls result in a resolved promise with a 200 status | ||
greed: 'none' | ||
afterEach(() => { | ||
fetchMock.restore(); | ||
}); | ||
thingToTest.exec(); | ||
// returns an array of calls to the session service, | ||
// each item in the array is an array of the arguments passed to fetch | ||
// similar to sinon.spy.args | ||
fetchMock.calls('session') // non empty array | ||
fetchMock.called('geo') // Boolean | ||
// reset all call logs | ||
fetchMock.reset() | ||
fetchMock.calls('session') // undefined | ||
fetchMock.called('geo') // false | ||
// fetch itself is just an ordinary sinon.stub | ||
fetch.calledWith('thing') | ||
// restores fetch and resets all data | ||
fetchMock.restore(); | ||
}) | ||
describe('content', function () { | ||
before(function () { | ||
// register an additional route, this one has a more complex matching rule | ||
fetchMock.registerRoute('content', function (url, opts) { | ||
return opts.headers.get('x-api-key') && url.test(/^https\:\/\/contentservice\.host\.com/); | ||
}, {body: 'I am an article'}); | ||
it('should be possible to augment persistent set of routes', () => { | ||
fetchMock.mock('http://content.service.com/contentid', {content: 'blah blah'}) | ||
page.init(); | ||
expect(fetchMock.called('http://content.service.com/contentid')).to.be.true; | ||
}); | ||
after(function () { | ||
fetchMock.unregisterRoute('content'); | ||
}) | ||
it('should do B', function () { | ||
fetchMock.mock({ | ||
// you can choose to mock a subset of the registered routes | ||
// and even add one to be mocked for this test only | ||
// - the route will exist until fetchMock.restore() is called | ||
routes: ['session', 'content', { | ||
name: 'enhanced-content', | ||
matcher: /^https\:\/\/enhanced-contentservice\.host\.com/, | ||
// responses can be contextual depending on the request | ||
// url and opts parameters are exactly what would be passed to fetch | ||
response: function (url, opts) { | ||
return {body: 'enhanced-article-' + url.split('article-id/')[1]}; | ||
} | ||
}] | ||
}); | ||
thingToTest.exec(); | ||
fetchMock.calls('content') // non empty array | ||
fetchMock.called('enhanced-content') // Boolean | ||
// restores fetch and resets all data | ||
fetchMock.restore(); | ||
}) | ||
it('should do C', function () { | ||
fetchMock.mock({ | ||
// you can override the response for a service for this test only | ||
// this means e.g. you can configure an authentication service to return | ||
// a valid user normally, but only return invalid for the one test | ||
// where you're testing authentication | ||
responses: { | ||
'session': 'invalid-user' | ||
} | ||
}); | ||
thingToTest.exec(); | ||
// restores fetch and resets all data | ||
fetchMock.restore(); | ||
}) | ||
}); | ||
``` | ||
``` | ||
* Defining two routes with the same name will no longer throw an error (previous implementation was buggy anyway) | ||
* Added `lastCall()`, `lastUrl()` and `lastOptions()` utilities |
@@ -231,4 +231,5 @@ 'use strict'; | ||
this.addRoutes(config.routes); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
@@ -250,4 +251,2 @@ } | ||
* a promise of a Response, or forwading to native fetch | ||
* Has a helper method .augment(routes), which can be used to add additional | ||
* routes to the router | ||
*/ | ||
@@ -257,7 +256,7 @@ constructMock (config) { | ||
config = config || {}; | ||
const router = this.constructRouter(config); | ||
this.addRoutes(config.routes); | ||
config.greed = config.greed || 'none'; | ||
const mock = (url, opts) => { | ||
const response = router(url, opts); | ||
const response = this.router(url, opts); | ||
if (response) { | ||
@@ -282,98 +281,54 @@ debug('response found for ' + url); | ||
mock.augment = function (routes) { | ||
router.augment(routes); | ||
} | ||
return mock; | ||
} | ||
/** | ||
* constructRouter | ||
* Constructs a function which identifies if calls to fetch match any of the configured routes | ||
* and returns the Response defined by the route | ||
* @param {Object} config Can define routes and/or responses, which will be used to augment any | ||
* previously set by registerRoute() | ||
* @return {Function} Function expecting url + options or a Request object, and returning | ||
* a response config or undefined. | ||
* Has a helper method .augment(routes), which can be used to add additional | ||
* routes to the router | ||
* router | ||
* Given url + options or a Request object, checks to see if ait is matched by any routes and returns | ||
* config for a response or undefined. | ||
* @param {String|Request} url | ||
* @param {Object} | ||
* @return {Object} | ||
*/ | ||
constructRouter (config) { | ||
debug('building router'); | ||
router (url, opts) { | ||
let response; | ||
debug('searching for matching route for ' + url); | ||
this.routes.some(route => { | ||
let routes; | ||
if (route.matcher(url, opts)) { | ||
debug('Found matching route (' + route.name + ') for ' + url); | ||
this.push(route.name, [url, opts]); | ||
if (config.routes) { | ||
debug('applying one time only routes'); | ||
if (!(config.routes instanceof Array)) { | ||
config.routes = [config.routes]; | ||
} | ||
debug('Setting response for ' + route.name); | ||
response = route.response; | ||
const preRegisteredRoutes = {}; | ||
this.routes.forEach(route => { | ||
preRegisteredRoutes[route.name] = route; | ||
}); | ||
// Allows selective application of some of the preregistered routes | ||
routes = config.routes.map(route => { | ||
if (typeof route === 'string') { | ||
debug('applying preregistered route ' + route); | ||
return preRegisteredRoutes[route]; | ||
} else { | ||
debug('applying one time route ' + route.name); | ||
return compileRoute(route); | ||
if (typeof response === 'function') { | ||
debug('Constructing dynamic response for ' + route.name); | ||
response = response(url, opts); | ||
} | ||
}); | ||
} else if (this.routes.length) { | ||
debug('no one time only routes defined. Using preregistered routes only'); | ||
routes = [].slice.call(this.routes); | ||
} else { | ||
throw new Error('When no preconfigured routes set using .registerRoute(), .mock() must be passed configuration for routes') | ||
} | ||
const routeNames = {}; | ||
routes.forEach(route => { | ||
if (routeNames[route.name]) { | ||
throw new Error('Route names must be unique'); | ||
return true; | ||
} | ||
routeNames[route.name] = true; | ||
}); | ||
config.responses = config.responses || {}; | ||
debug('returning response for ' + url); | ||
return response; | ||
} | ||
const router = (url, opts) => { | ||
let response; | ||
debug('searching for matching route for ' + url); | ||
routes.some(route => { | ||
/** | ||
* addRoutes | ||
* Adds routes to those used by fetchMock to match fetch calls | ||
* @param {Object|Array} routes route configurations | ||
*/ | ||
addRoutes (routes) { | ||
if (route.matcher(url, opts)) { | ||
debug('Found matching route (' + route.name + ') for ' + url); | ||
this.push(route.name, [url, opts]); | ||
if (!routes) { | ||
throw new Error('.mock() must be passed configuration for routes') | ||
} | ||
if (config.responses[route.name]) { | ||
debug('Overriding response for ' + route.name); | ||
response = config.responses[route.name]; | ||
} else { | ||
debug('Using default response for ' + route.name); | ||
response = route.response; | ||
} | ||
if (typeof response === 'function') { | ||
debug('Constructing dynamic response for ' + route.name); | ||
response = response(url, opts); | ||
} | ||
return true; | ||
} | ||
}); | ||
debug('returning response for ' + url); | ||
return response; | ||
}; | ||
router.augment = function (additionalRoutes) { | ||
routes = routes.concat(additionalRoutes.map(compileRoute)); | ||
debug('applying one time only routes'); | ||
if (!(routes instanceof Array)) { | ||
routes = [routes]; | ||
} | ||
return router; | ||
// Allows selective application of some of the preregistered routes | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
} | ||
@@ -385,3 +340,3 @@ | ||
* @param {String} name Name of the route matched by the call | ||
* @param {Object} call {url, opts} pair | ||
* @param {Array} call [url, opts] pair | ||
*/ | ||
@@ -407,2 +362,3 @@ push (name, call) { | ||
this.reset(); | ||
this.routes = []; | ||
debug('fetch restored'); | ||
@@ -452,2 +408,21 @@ } | ||
lastCall (name) { | ||
const calls = name ? this.calls(name) : this.calls().matched; | ||
if (calls && calls.length) { | ||
return calls[calls.length - 1]; | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
lastUrl (name) { | ||
const call = this.lastCall(name); | ||
return call && call[0]; | ||
} | ||
lastOptions (name) { | ||
const call = this.lastCall(name); | ||
return call && call[1]; | ||
} | ||
/** | ||
@@ -463,56 +438,4 @@ * called | ||
} | ||
/** | ||
* registerRoute | ||
* Creates a route that will persist even when fetchMock.restore() is called | ||
* See README for details of parameters | ||
*/ | ||
registerRoute (name, matcher, response) { | ||
debug('registering routes'); | ||
let routes; | ||
if (name instanceof Array) { | ||
routes = name; | ||
} else if (arguments.length === 3 ) { | ||
routes = [{ | ||
name, | ||
matcher, | ||
response, | ||
}]; | ||
} else { | ||
routes = [name]; | ||
} | ||
debug('registering routes: ' + routes.map(r => r.name)); | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
} | ||
/** | ||
* unregisterRoute | ||
* Removes a persistent route | ||
* See README for details of parameters | ||
*/ | ||
unregisterRoute (names) { | ||
if (!names) { | ||
debug('unregistering all routes'); | ||
this.routes = []; | ||
return; | ||
} | ||
if (!(names instanceof Array)) { | ||
names = [names]; | ||
} | ||
debug('unregistering routes: ' + names); | ||
this.routes = this.routes.filter(route => { | ||
const keep = names.indexOf(route.name) === -1; | ||
if (!keep) { | ||
debug('unregistering route ' + route.name); | ||
} | ||
return keep; | ||
}); | ||
} | ||
} | ||
module.exports = FetchMock; |
@@ -28,3 +28,3 @@ 'use strict'; | ||
expect(fetchMock.realFetch).to.equal(dummyFetch); | ||
const mock = fetchMock.mock().getMock(); | ||
const mock = fetchMock.mock(/a/,200).getMock(); | ||
expect(typeof mock).to.equal('function'); | ||
@@ -31,0 +31,0 @@ expect(function () { |
310
test/spec.js
@@ -142,13 +142,2 @@ 'use strict'; | ||
}); | ||
it('expects unique route names', function () { | ||
expect(function () { | ||
fetchMock.mock({ | ||
routes: [ | ||
{name: 'route', matcher: 'http://it.at.there/', response: 'ok'}, | ||
{name: 'route', matcher: 'http://it.at.here/', response: 'ok'} | ||
] | ||
}); | ||
}).to.throw(); | ||
}); | ||
}); | ||
@@ -476,2 +465,30 @@ | ||
it('have helpers to retrieve paramaters pf last call', function (done) { | ||
fetchMock.mock({ | ||
routes: { | ||
name: 'route', | ||
matcher: '^http://it.at.there', | ||
response: 200 | ||
} | ||
}); | ||
// fail gracefully | ||
expect(function () { | ||
fetchMock.lastCall(); | ||
fetchMock.lastUrl(); | ||
fetchMock.lastOptions(); | ||
}).to.not.throw; | ||
Promise.all([ | ||
fetch('http://it.at.there/first', {method: 'DELETE'}), | ||
fetch('http://it.at.there/second', {method: 'GET'}) | ||
]) | ||
.then(function () { | ||
expect(fetchMock.lastCall('route')).to.deep.equal(['http://it.at.there/second', {method: 'GET'}]); | ||
expect(fetchMock.lastCall()).to.deep.equal(['http://it.at.there/second', {method: 'GET'}]); | ||
expect(fetchMock.lastUrl()).to.equal('http://it.at.there/second'); | ||
expect(fetchMock.lastOptions()).to.deep.equal({method: 'GET'}); | ||
done(); | ||
}); | ||
}) | ||
it('be possible to reset call history', function (done) { | ||
@@ -651,274 +668,3 @@ fetchMock.mock({ | ||
describe('persistent route config', function () { | ||
beforeEach(function () { | ||
try { | ||
fetchMock.restore(); | ||
} catch (e) {} | ||
fetchMock.unregisterRoute(); | ||
}); | ||
it('register a single route', function (done) { | ||
fetchMock.registerRoute('route', 'http://it.at.there/', 'a string'); | ||
fetchMock.mock(); | ||
fetch('http://it.at.there/') | ||
.then(function () { | ||
expect(fetchMock.calls('route').length).to.equal(1); | ||
expect(fetchMock.calls().matched.length).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
it('register a single route as an object', function (done) { | ||
fetchMock.registerRoute({ | ||
name: 'route', | ||
matcher: 'http://it.at.there/', | ||
response: 'ok' | ||
}); | ||
fetchMock.mock(); | ||
fetch('http://it.at.there/') | ||
.then(function () { | ||
expect(fetchMock.calls('route').length).to.equal(1); | ||
expect(fetchMock.calls().matched.length).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
it('register multiple routes', function (done) { | ||
fetchMock.registerRoute([{ | ||
name: 'route1', | ||
matcher: 'http://it.at.there/', | ||
response: 'ok' | ||
}, { | ||
name: 'route2', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}]); | ||
fetchMock.mock(); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/')]) | ||
.then(function () { | ||
expect(fetchMock.calls('route1').length).to.equal(1); | ||
expect(fetchMock.calls('route2').length).to.equal(1); | ||
expect(fetchMock.calls().matched.length).to.equal(2); | ||
done(); | ||
}); | ||
}); | ||
it('expects unique route names', function () { | ||
expect(function () { | ||
fetchMock.registerRoute([{ | ||
name: 'route', | ||
matcher: 'http://it.at.there/', | ||
response: 'ok' | ||
}, { | ||
name: 'route', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}]); | ||
fetchMock(); | ||
}).to.throw(); | ||
}); | ||
it('register routes multiple times', function (done) { | ||
fetchMock.registerRoute('route1', 'http://it.at.there/', 'a string'); | ||
fetchMock.registerRoute('route2', 'http://it.at.here/', 'a string'); | ||
fetchMock.mock(); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/')]) | ||
.then(function () { | ||
expect(fetchMock.calls('route1').length).to.equal(1); | ||
expect(fetchMock.calls('route2').length).to.equal(1); | ||
expect(fetchMock.calls().matched.length).to.equal(2); | ||
done(); | ||
}) | ||
}); | ||
it('unregister a single route', function (done) { | ||
fetchMock.registerRoute([{ | ||
name: 'route1', | ||
matcher: 'http://it.at.there/', | ||
response: 'ok' | ||
}, { | ||
name: 'route2', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}, { | ||
name: 'route3', | ||
matcher: 'http://it.at.where/', | ||
response: 'ok' | ||
}]); | ||
fetchMock.unregisterRoute('route2'); | ||
fetchMock.mock(); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/')]) | ||
.then(function () { | ||
expect(fetchMock.calls('route1').length).to.equal(1); | ||
expect(fetchMock.calls('route2').length).to.equal(0); | ||
expect(fetchMock.calls().unmatched.length).to.equal(1); | ||
expect(fetchMock.calls().matched.length).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
it('unregister multiple routes', function (done) { | ||
fetchMock.registerRoute([{ | ||
name: 'route1', | ||
matcher: 'http://it.at.there/', | ||
response: 'ok' | ||
}, { | ||
name: 'route2', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}, { | ||
name: 'route3', | ||
matcher: 'http://it.at.where/', | ||
response: 'ok' | ||
}]); | ||
fetchMock.unregisterRoute(['route1', 'route2']); | ||
fetchMock.mock(); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/'), fetch('http://it.at.where/')]) | ||
.then(function () { | ||
expect(fetchMock.calls('route3').length).to.equal(1); | ||
expect(fetchMock.calls('route1').length).to.equal(0); | ||
expect(fetchMock.calls('route2').length).to.equal(0); | ||
expect(fetchMock.calls().unmatched.length).to.equal(2); | ||
expect(fetchMock.calls().matched.length).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
it('preserve registered routes from test to test', function (done) { | ||
fetchMock.registerRoute('route', 'http://it.at.there/', 'a string'); | ||
fetchMock.mock(); | ||
fetch('http://it.at.there/') | ||
.then(function () { | ||
expect(fetchMock.calls('route').length).to.equal(1); | ||
fetchMock.restore(); | ||
expect(fetchMock.calls('route').length).to.equal(0); | ||
fetchMock.mock(); | ||
fetch('http://it.at.there/') | ||
.then(function () { | ||
expect(fetchMock.calls('route').length).to.equal(1); | ||
fetchMock.restore(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('use selection of registered routes', function (done) { | ||
fetchMock.registerRoute([{ | ||
name: 'route1', | ||
matcher: 'http://it.at.there/', | ||
response: 'ok' | ||
}, { | ||
name: 'route2', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}, { | ||
name: 'route3', | ||
matcher: 'http://it.at.where/', | ||
response: 'ok' | ||
}]); | ||
fetchMock.mock({ | ||
routes: ['route3', 'route1'] | ||
}); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/'), fetch('http://it.at.where/')]) | ||
.then(function () { | ||
expect(fetchMock.calls('route3').length).to.equal(1); | ||
expect(fetchMock.calls('route1').length).to.equal(1); | ||
expect(fetchMock.calls('route2').length).to.equal(0); | ||
expect(fetchMock.calls().matched.length).to.equal(2); | ||
expect(fetchMock.calls().unmatched.length).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
it('mix one off routes with registered routes', function (done) { | ||
fetchMock.registerRoute({ | ||
name: 'route1', | ||
matcher: 'http://it.at.there/', | ||
response: 'ok' | ||
}); | ||
fetchMock.mock({ | ||
routes: [{ | ||
name: 'route2', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}, 'route1'] | ||
}); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/')]) | ||
.then(function () { | ||
expect(fetchMock.calls('route2').length).to.equal(1); | ||
expect(fetchMock.calls('route1').length).to.equal(1); | ||
expect(fetchMock.calls().matched.length).to.equal(2); | ||
done(); | ||
}); | ||
}); | ||
it('apply routes in specified order', function (done) { | ||
fetchMock.registerRoute({ | ||
name: 'route1', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}); | ||
fetchMock.mock({ | ||
routes: [{ | ||
name: 'route2', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}, 'route1'] | ||
}); | ||
fetch('http://it.at.here/') | ||
.then(function () { | ||
expect(fetchMock.calls('route2').length).to.equal(1); | ||
expect(fetchMock.calls('route1').length).to.equal(0); | ||
expect(fetchMock.calls().matched.length).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
it('override response for a registered route', function (done) { | ||
fetchMock.registerRoute({ | ||
name: 'route1', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}); | ||
fetchMock.mock({ | ||
responses: { | ||
route1: 'changed my mind' | ||
} | ||
}); | ||
fetch('http://it.at.here/') | ||
.then(function (res) { | ||
res.text().then(function (text) { | ||
expect(text).to.equal('changed my mind'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('apply overrides when mock already mocking', function (done) { | ||
fetchMock.registerRoute({ | ||
name: 'route1', | ||
matcher: 'http://it.at.here/', | ||
response: 'ok' | ||
}); | ||
fetchMock.mock(); | ||
fetchMock.reMock({ | ||
responses: { | ||
route1: 'changed my mind' | ||
} | ||
}); | ||
fetch('http://it.at.here/') | ||
.then(function (res) { | ||
res.text().then(function (text) { | ||
expect(text).to.equal('changed my mind'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
60
71919
1967
156