Socket
Socket
Sign inDemoInstall

fetch-mock

Package Overview
Dependencies
Maintainers
1
Versions
226
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fetch-mock - npm Package Compare versions

Comparing version 3.2.1 to 4.0.0

218

es5/client-browserified.js

@@ -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",

# 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 () {

@@ -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();
});
});
});
});
});
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc