fetch-mock
Advanced tools
Comparing version 3.2.0 to 3.2.1
@@ -11,3 +11,2 @@ (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){ | ||
Headers: window.Headers, | ||
Blob: window.Blob, | ||
debug: function debug() {} | ||
@@ -29,9 +28,20 @@ }); | ||
var stream = undefined; | ||
var Blob = undefined; | ||
var theGlobal = undefined; | ||
var debug = undefined; | ||
/** | ||
* mockResponse | ||
* Constructs a Response object to return from the mocked fetch | ||
* @param {String} url url parameter fetch was called with | ||
* @param {Object} config configuration for the response to be constructed | ||
* @return {Promise} Promise for a Response object (or a rejected response to imitate network failure) | ||
*/ | ||
function mockResponse(url, config) { | ||
debug('mocking response for ' + url); | ||
// allow just body to be passed in as this is the commonest use case | ||
if (config.throws) { | ||
debug('mocking failed request for ' + url); | ||
return Promise.reject(config.throws); | ||
} | ||
if (typeof config === 'number') { | ||
@@ -51,17 +61,13 @@ debug('status response detected for ' + url); | ||
if (config.throws) { | ||
debug('mocking failed request for ' + url); | ||
return Promise.reject(config.throws); | ||
} | ||
var opts = config.opts || {}; | ||
opts.url = url; | ||
opts.status = config.status || 200; | ||
// the ternary oprator is to cope with new Headers(undefined) throwing in chrome | ||
// (unclear to me if this is a bug or if the specification says this is correct behaviour) | ||
// The ternary operator is to cope with new Headers(undefined) throwing in Chrome | ||
// https://code.google.com/p/chromium/issues/detail?id=335871 | ||
opts.headers = config.headers ? new Headers(config.headers) : new Headers(); | ||
var body = config.body; | ||
/*eslint-disable*/ | ||
if (config.body != null && (typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') { | ||
/*eslint-enable*/ | ||
body = JSON.stringify(body); | ||
@@ -74,3 +80,5 @@ } | ||
var s = new stream.Readable(); | ||
/*eslint-disable*/ | ||
if (body != null) { | ||
/*eslint-enable*/ | ||
s.push(body, 'utf-8'); | ||
@@ -85,2 +93,10 @@ } | ||
/** | ||
* normalizeRequest | ||
* Given the parameters fetch was called with, normalises Request or url + options pairs | ||
* to a standard container object passed to matcher functions | ||
* @param {String|Request} url | ||
* @param {Object} options | ||
* @return {Object} {url, method} | ||
*/ | ||
function normalizeRequest(url, options) { | ||
@@ -100,22 +116,19 @@ if (Request.prototype.isPrototypeOf(url)) { | ||
/** | ||
* compileRoute | ||
* Given a route configuration object, validates the object structure and compiles | ||
* the object into a {name, matcher, response} triple | ||
* @param {Object} route route config | ||
* @return {Object} {name, matcher, response} | ||
*/ | ||
function compileRoute(route) { | ||
var method = route.method; | ||
var matchMethod; | ||
if (method) { | ||
method = method.toLowerCase(); | ||
matchMethod = function (req) { | ||
var m = req && req.method ? req.method.toLowerCase() : 'get'; | ||
return m === method; | ||
}; | ||
} else { | ||
matchMethod = function () { | ||
return true; | ||
}; | ||
debug('compiling route: ' + route.name); | ||
if (typeof route.response === 'undefined') { | ||
throw new Error('Each route must define a response'); | ||
} | ||
debug('compiling route: ' + route.name); | ||
if (!route.matcher) { | ||
throw 'each route must specify a string, regex or function to match calls to fetch'; | ||
throw new Error('each route must specify a string, regex or function to match calls to fetch'); | ||
} | ||
@@ -128,24 +141,35 @@ | ||
if (typeof route.response === 'undefined') { | ||
throw 'each route must define a response'; | ||
// If user has provided a function as a matcher we assume they are handling all the | ||
// matching logic they need | ||
if (typeof route.matcher === 'function') { | ||
return route; | ||
} | ||
var expectedMethod = route.method && route.method.toLowerCase(); | ||
function matchMethod(method) { | ||
return !expectedMethod || expectedMethod === (method ? method.toLowerCase() : 'get'); | ||
}; | ||
var matchUrl = undefined; | ||
if (typeof route.matcher === 'string') { | ||
(function () { | ||
var expectedUrl = route.matcher; | ||
if (route.matcher.indexOf('^') === 0) { | ||
if (route.matcher.indexOf('^') === 0) { | ||
(function () { | ||
debug('constructing starts with string matcher for route: ' + route.name); | ||
expectedUrl = expectedUrl.substr(1); | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && req.url.indexOf(expectedUrl) === 0; | ||
var expectedUrl = route.matcher.substr(1); | ||
matchUrl = function (url) { | ||
return url.indexOf(expectedUrl) === 0; | ||
}; | ||
} else { | ||
})(); | ||
} else { | ||
(function () { | ||
debug('constructing string matcher for route: ' + route.name); | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && req.url === expectedUrl; | ||
var expectedUrl = route.matcher; | ||
matchUrl = function (url) { | ||
return url === expectedUrl; | ||
}; | ||
} | ||
})(); | ||
})(); | ||
} | ||
} else if (route.matcher instanceof RegExp) { | ||
@@ -155,8 +179,13 @@ (function () { | ||
var urlRX = route.matcher; | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && urlRX.test(req.url); | ||
matchUrl = function (url) { | ||
return urlRX.test(url); | ||
}; | ||
})(); | ||
} | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req.method) && matchUrl(req.url); | ||
}; | ||
return route; | ||
@@ -166,2 +195,8 @@ } | ||
var FetchMock = (function () { | ||
/** | ||
* constructor | ||
* Sets up scoped references to configuration passed in from client/server bootstrappers | ||
* @param {Object} opts | ||
*/ | ||
function FetchMock(opts) { | ||
@@ -174,3 +209,2 @@ _classCallCheck(this, FetchMock); | ||
stream = opts.stream; | ||
Blob = opts.Blob; | ||
theGlobal = opts.theGlobal; | ||
@@ -180,2 +214,4 @@ debug = opts.debug; | ||
this._calls = {}; | ||
this._matchedCalls = []; | ||
this._unmatchedCalls = []; | ||
this.mockedContext = theGlobal; | ||
@@ -185,2 +221,8 @@ this.realFetch = theGlobal.fetch && theGlobal.fetch.bind(theGlobal); | ||
/** | ||
* useNonGlobalFetch | ||
* Sets fetchMock's default internal reference to native fetch to the given function | ||
* @param {Function} func | ||
*/ | ||
_createClass(FetchMock, [{ | ||
@@ -192,52 +234,121 @@ key: 'useNonGlobalFetch', | ||
} | ||
/** | ||
* mock | ||
* Replaces fetch with a stub which attempts to match calls against configured routes | ||
* See README for details of parameters | ||
* @return {FetchMock} Returns the FetchMock instance, so can be chained | ||
*/ | ||
}, { | ||
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 | ||
}]; | ||
key: 'mock', | ||
value: function mock(matcher, method, response) { | ||
var config = undefined; | ||
// Handle the variety of parameters accepted by mock (see README) | ||
if (response) { | ||
config = { | ||
routes: [{ | ||
matcher: matcher, | ||
method: method, | ||
response: response | ||
}] | ||
}; | ||
} else if (method) { | ||
config = { | ||
routes: [{ | ||
matcher: matcher, | ||
response: method | ||
}] | ||
}; | ||
} else if (matcher instanceof Array) { | ||
config = { | ||
routes: matcher | ||
}; | ||
} else if (matcher && matcher.matcher) { | ||
config = { | ||
routes: [matcher] | ||
}; | ||
} else { | ||
routes = [name]; | ||
config = matcher; | ||
} | ||
debug('registering routes: ' + routes.map(function (r) { | ||
return r.name; | ||
})); | ||
debug('mocking fetch'); | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
} | ||
this.isMocking = true; | ||
this.mockedContext.fetch = this.constructMock(config); | ||
return this; | ||
} | ||
/** | ||
* constructMock | ||
* Constructs a function which attempts to match fetch calls against routes (see constructRouter) | ||
* and handles success or failure of that attempt accordingly | ||
* @param {Object} config See README | ||
* @return {Function} Function expecting url + options or a Request object, and returning | ||
* 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 | ||
*/ | ||
}, { | ||
key: 'unregisterRoute', | ||
value: function unregisterRoute(names) { | ||
key: 'constructMock', | ||
value: function constructMock(config) { | ||
var _this = this; | ||
if (!names) { | ||
debug('unregistering all routes'); | ||
this.routes = []; | ||
return; | ||
} | ||
if (!(names instanceof Array)) { | ||
names = [names]; | ||
} | ||
debug('constructing mock function'); | ||
config = config || {}; | ||
var router = this.constructRouter(config); | ||
config.greed = config.greed || 'none'; | ||
debug('unregistering routes: ' + names); | ||
var mock = function mock(url, opts) { | ||
var response = router(url, opts); | ||
if (response) { | ||
debug('response found for ' + url); | ||
return mockResponse(url, response); | ||
} else { | ||
debug('response not found for ' + url); | ||
_this.push(null, [url, opts]); | ||
if (config.greed === 'good') { | ||
debug('sending default good response'); | ||
return mockResponse(url, { body: 'unmocked url: ' + url }); | ||
} else if (config.greed === 'bad') { | ||
debug('sending default bad response'); | ||
return mockResponse(url, { throws: 'unmocked url: ' + url }); | ||
} else { | ||
debug('forwarding to default fetch'); | ||
return _this.realFetch(url, opts); | ||
} | ||
} | ||
}; | ||
this.routes = this.routes.filter(function (route) { | ||
var keep = names.indexOf(route.name) === -1; | ||
if (!keep) { | ||
debug('unregistering route ' + route.name); | ||
} | ||
return keep; | ||
}); | ||
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 | ||
*/ | ||
}, { | ||
key: 'getRouter', | ||
value: function getRouter(config) { | ||
var _this = this; | ||
key: 'constructRouter', | ||
value: function constructRouter(config) { | ||
var _this2 = this; | ||
@@ -256,5 +367,7 @@ debug('building router'); | ||
var preRegisteredRoutes = {}; | ||
_this.routes.forEach(function (route) { | ||
_this2.routes.forEach(function (route) { | ||
preRegisteredRoutes[route.name] = route; | ||
}); | ||
// Allows selective application of some of the preregistered routes | ||
routes = config.routes.map(function (route) { | ||
@@ -270,5 +383,7 @@ if (typeof route === 'string') { | ||
})(); | ||
} else { | ||
} 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'); | ||
} | ||
@@ -279,3 +394,3 @@ | ||
if (routeNames[route.name]) { | ||
throw 'Route names must be unique'; | ||
throw new Error('Route names must be unique'); | ||
} | ||
@@ -294,3 +409,3 @@ routeNames[route.name] = true; | ||
debug('Found matching route (' + route.name + ') for ' + url); | ||
_this.push(route.name, [url, opts]); | ||
_this2.push(route.name, [url, opts]); | ||
@@ -323,2 +438,10 @@ if (config.responses[route.name]) { | ||
} | ||
/** | ||
* push | ||
* Records history of fetch calls | ||
* @param {String} name Name of the route matched by the call | ||
* @param {Object} call {url, opts} pair | ||
*/ | ||
}, { | ||
@@ -335,87 +458,9 @@ key: 'push', | ||
} | ||
}, { | ||
key: 'mock', | ||
value: function mock(matcher, method, response) { | ||
var config = undefined; | ||
if (response) { | ||
/** | ||
* restore | ||
* Restores global fetch to its initial state and resets call history | ||
*/ | ||
config = { | ||
routes: [{ | ||
matcher: matcher, | ||
method: method, | ||
response: response | ||
}] | ||
}; | ||
} else if (method) { | ||
config = { | ||
routes: [{ | ||
matcher: matcher, | ||
response: method | ||
}] | ||
}; | ||
} else if (matcher instanceof Array) { | ||
config = { | ||
routes: matcher | ||
}; | ||
} else if (matcher && matcher.matcher) { | ||
config = { | ||
routes: [matcher] | ||
}; | ||
} else { | ||
config = matcher; | ||
} | ||
debug('mocking fetch'); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
} | ||
this.isMocking = true; | ||
this._matchedCalls = []; | ||
this._unmatchedCalls = []; | ||
this.mockedContext.fetch = this.constructMock(config); | ||
return this; | ||
} | ||
}, { | ||
key: 'constructMock', | ||
value: function constructMock(config) { | ||
var _this2 = this; | ||
debug('constructing mock function'); | ||
config = config || {}; | ||
var router = this.getRouter(config); | ||
config.greed = config.greed || 'none'; | ||
var mock = function mock(url, opts) { | ||
var response = router(url, opts); | ||
if (response) { | ||
debug('response found for ' + url); | ||
return mockResponse(url, response); | ||
} else { | ||
debug('response not found for ' + url); | ||
_this2.push(null, [url, opts]); | ||
if (config.greed === 'good') { | ||
debug('sending default good response'); | ||
return mockResponse(url, { body: 'unmocked url: ' + url }); | ||
} else if (config.greed === 'bad') { | ||
debug('sending default bad response'); | ||
return mockResponse(url, { throws: 'unmocked url: ' + url }); | ||
} else { | ||
debug('forwarding to default fetch'); | ||
return _this2.realFetch(url, opts); | ||
} | ||
} | ||
}; | ||
mock.augment = function (routes) { | ||
router.augment(routes); | ||
}; | ||
return mock; | ||
} | ||
}, { | ||
key: 'restore', | ||
@@ -429,2 +474,9 @@ value: function restore() { | ||
} | ||
/** | ||
* reMock | ||
* Same as .mock(), but also calls .restore() internally | ||
* @return {FetchMock} Returns the FetchMock instance, so can be chained | ||
*/ | ||
}, { | ||
@@ -436,2 +488,9 @@ key: 'reMock', | ||
} | ||
/** | ||
* getMock | ||
* Returns a reference to the stub function used to mock fetch | ||
* @return {Function} | ||
*/ | ||
}, { | ||
@@ -442,2 +501,8 @@ key: 'getMock', | ||
} | ||
/** | ||
* reset | ||
* Resets call history | ||
*/ | ||
}, { | ||
@@ -451,2 +516,8 @@ key: 'reset', | ||
} | ||
/** | ||
* calls | ||
* Returns call history. See README | ||
*/ | ||
}, { | ||
@@ -460,2 +531,8 @@ key: 'calls', | ||
} | ||
/** | ||
* called | ||
* Returns whether fetch has been called matching a configured route. See README | ||
*/ | ||
}, { | ||
@@ -469,2 +546,62 @@ key: 'called', | ||
} | ||
/** | ||
* 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; | ||
}); | ||
} | ||
}]); | ||
@@ -471,0 +608,0 @@ |
@@ -10,4 +10,3 @@ 'use strict'; | ||
Headers: window.Headers, | ||
Blob: window.Blob, | ||
debug: function debug() {} | ||
}); |
@@ -13,9 +13,20 @@ 'use strict'; | ||
var stream = undefined; | ||
var Blob = undefined; | ||
var theGlobal = undefined; | ||
var debug = undefined; | ||
/** | ||
* mockResponse | ||
* Constructs a Response object to return from the mocked fetch | ||
* @param {String} url url parameter fetch was called with | ||
* @param {Object} config configuration for the response to be constructed | ||
* @return {Promise} Promise for a Response object (or a rejected response to imitate network failure) | ||
*/ | ||
function mockResponse(url, config) { | ||
debug('mocking response for ' + url); | ||
// allow just body to be passed in as this is the commonest use case | ||
if (config.throws) { | ||
debug('mocking failed request for ' + url); | ||
return Promise.reject(config.throws); | ||
} | ||
if (typeof config === 'number') { | ||
@@ -35,17 +46,13 @@ debug('status response detected for ' + url); | ||
if (config.throws) { | ||
debug('mocking failed request for ' + url); | ||
return Promise.reject(config.throws); | ||
} | ||
var opts = config.opts || {}; | ||
opts.url = url; | ||
opts.status = config.status || 200; | ||
// the ternary oprator is to cope with new Headers(undefined) throwing in chrome | ||
// (unclear to me if this is a bug or if the specification says this is correct behaviour) | ||
// The ternary operator is to cope with new Headers(undefined) throwing in Chrome | ||
// https://code.google.com/p/chromium/issues/detail?id=335871 | ||
opts.headers = config.headers ? new Headers(config.headers) : new Headers(); | ||
var body = config.body; | ||
/*eslint-disable*/ | ||
if (config.body != null && (typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') { | ||
/*eslint-enable*/ | ||
body = JSON.stringify(body); | ||
@@ -58,3 +65,5 @@ } | ||
var s = new stream.Readable(); | ||
/*eslint-disable*/ | ||
if (body != null) { | ||
/*eslint-enable*/ | ||
s.push(body, 'utf-8'); | ||
@@ -69,2 +78,10 @@ } | ||
/** | ||
* normalizeRequest | ||
* Given the parameters fetch was called with, normalises Request or url + options pairs | ||
* to a standard container object passed to matcher functions | ||
* @param {String|Request} url | ||
* @param {Object} options | ||
* @return {Object} {url, method} | ||
*/ | ||
function normalizeRequest(url, options) { | ||
@@ -84,22 +101,19 @@ if (Request.prototype.isPrototypeOf(url)) { | ||
/** | ||
* compileRoute | ||
* Given a route configuration object, validates the object structure and compiles | ||
* the object into a {name, matcher, response} triple | ||
* @param {Object} route route config | ||
* @return {Object} {name, matcher, response} | ||
*/ | ||
function compileRoute(route) { | ||
var method = route.method; | ||
var matchMethod; | ||
if (method) { | ||
method = method.toLowerCase(); | ||
matchMethod = function (req) { | ||
var m = req && req.method ? req.method.toLowerCase() : 'get'; | ||
return m === method; | ||
}; | ||
} else { | ||
matchMethod = function () { | ||
return true; | ||
}; | ||
debug('compiling route: ' + route.name); | ||
if (typeof route.response === 'undefined') { | ||
throw new Error('Each route must define a response'); | ||
} | ||
debug('compiling route: ' + route.name); | ||
if (!route.matcher) { | ||
throw 'each route must specify a string, regex or function to match calls to fetch'; | ||
throw new Error('each route must specify a string, regex or function to match calls to fetch'); | ||
} | ||
@@ -112,24 +126,35 @@ | ||
if (typeof route.response === 'undefined') { | ||
throw 'each route must define a response'; | ||
// If user has provided a function as a matcher we assume they are handling all the | ||
// matching logic they need | ||
if (typeof route.matcher === 'function') { | ||
return route; | ||
} | ||
var expectedMethod = route.method && route.method.toLowerCase(); | ||
function matchMethod(method) { | ||
return !expectedMethod || expectedMethod === (method ? method.toLowerCase() : 'get'); | ||
}; | ||
var matchUrl = undefined; | ||
if (typeof route.matcher === 'string') { | ||
(function () { | ||
var expectedUrl = route.matcher; | ||
if (route.matcher.indexOf('^') === 0) { | ||
if (route.matcher.indexOf('^') === 0) { | ||
(function () { | ||
debug('constructing starts with string matcher for route: ' + route.name); | ||
expectedUrl = expectedUrl.substr(1); | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && req.url.indexOf(expectedUrl) === 0; | ||
var expectedUrl = route.matcher.substr(1); | ||
matchUrl = function (url) { | ||
return url.indexOf(expectedUrl) === 0; | ||
}; | ||
} else { | ||
})(); | ||
} else { | ||
(function () { | ||
debug('constructing string matcher for route: ' + route.name); | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && req.url === expectedUrl; | ||
var expectedUrl = route.matcher; | ||
matchUrl = function (url) { | ||
return url === expectedUrl; | ||
}; | ||
} | ||
})(); | ||
})(); | ||
} | ||
} else if (route.matcher instanceof RegExp) { | ||
@@ -139,8 +164,13 @@ (function () { | ||
var urlRX = route.matcher; | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && urlRX.test(req.url); | ||
matchUrl = function (url) { | ||
return urlRX.test(url); | ||
}; | ||
})(); | ||
} | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req.method) && matchUrl(req.url); | ||
}; | ||
return route; | ||
@@ -150,2 +180,8 @@ } | ||
var FetchMock = (function () { | ||
/** | ||
* constructor | ||
* Sets up scoped references to configuration passed in from client/server bootstrappers | ||
* @param {Object} opts | ||
*/ | ||
function FetchMock(opts) { | ||
@@ -158,3 +194,2 @@ _classCallCheck(this, FetchMock); | ||
stream = opts.stream; | ||
Blob = opts.Blob; | ||
theGlobal = opts.theGlobal; | ||
@@ -164,2 +199,4 @@ debug = opts.debug; | ||
this._calls = {}; | ||
this._matchedCalls = []; | ||
this._unmatchedCalls = []; | ||
this.mockedContext = theGlobal; | ||
@@ -169,2 +206,8 @@ this.realFetch = theGlobal.fetch && theGlobal.fetch.bind(theGlobal); | ||
/** | ||
* useNonGlobalFetch | ||
* Sets fetchMock's default internal reference to native fetch to the given function | ||
* @param {Function} func | ||
*/ | ||
_createClass(FetchMock, [{ | ||
@@ -176,52 +219,121 @@ key: 'useNonGlobalFetch', | ||
} | ||
/** | ||
* mock | ||
* Replaces fetch with a stub which attempts to match calls against configured routes | ||
* See README for details of parameters | ||
* @return {FetchMock} Returns the FetchMock instance, so can be chained | ||
*/ | ||
}, { | ||
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 | ||
}]; | ||
key: 'mock', | ||
value: function mock(matcher, method, response) { | ||
var config = undefined; | ||
// Handle the variety of parameters accepted by mock (see README) | ||
if (response) { | ||
config = { | ||
routes: [{ | ||
matcher: matcher, | ||
method: method, | ||
response: response | ||
}] | ||
}; | ||
} else if (method) { | ||
config = { | ||
routes: [{ | ||
matcher: matcher, | ||
response: method | ||
}] | ||
}; | ||
} else if (matcher instanceof Array) { | ||
config = { | ||
routes: matcher | ||
}; | ||
} else if (matcher && matcher.matcher) { | ||
config = { | ||
routes: [matcher] | ||
}; | ||
} else { | ||
routes = [name]; | ||
config = matcher; | ||
} | ||
debug('registering routes: ' + routes.map(function (r) { | ||
return r.name; | ||
})); | ||
debug('mocking fetch'); | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
} | ||
this.isMocking = true; | ||
this.mockedContext.fetch = this.constructMock(config); | ||
return this; | ||
} | ||
/** | ||
* constructMock | ||
* Constructs a function which attempts to match fetch calls against routes (see constructRouter) | ||
* and handles success or failure of that attempt accordingly | ||
* @param {Object} config See README | ||
* @return {Function} Function expecting url + options or a Request object, and returning | ||
* 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 | ||
*/ | ||
}, { | ||
key: 'unregisterRoute', | ||
value: function unregisterRoute(names) { | ||
key: 'constructMock', | ||
value: function constructMock(config) { | ||
var _this = this; | ||
if (!names) { | ||
debug('unregistering all routes'); | ||
this.routes = []; | ||
return; | ||
} | ||
if (!(names instanceof Array)) { | ||
names = [names]; | ||
} | ||
debug('constructing mock function'); | ||
config = config || {}; | ||
var router = this.constructRouter(config); | ||
config.greed = config.greed || 'none'; | ||
debug('unregistering routes: ' + names); | ||
var mock = function mock(url, opts) { | ||
var response = router(url, opts); | ||
if (response) { | ||
debug('response found for ' + url); | ||
return mockResponse(url, response); | ||
} else { | ||
debug('response not found for ' + url); | ||
_this.push(null, [url, opts]); | ||
if (config.greed === 'good') { | ||
debug('sending default good response'); | ||
return mockResponse(url, { body: 'unmocked url: ' + url }); | ||
} else if (config.greed === 'bad') { | ||
debug('sending default bad response'); | ||
return mockResponse(url, { throws: 'unmocked url: ' + url }); | ||
} else { | ||
debug('forwarding to default fetch'); | ||
return _this.realFetch(url, opts); | ||
} | ||
} | ||
}; | ||
this.routes = this.routes.filter(function (route) { | ||
var keep = names.indexOf(route.name) === -1; | ||
if (!keep) { | ||
debug('unregistering route ' + route.name); | ||
} | ||
return keep; | ||
}); | ||
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 | ||
*/ | ||
}, { | ||
key: 'getRouter', | ||
value: function getRouter(config) { | ||
var _this = this; | ||
key: 'constructRouter', | ||
value: function constructRouter(config) { | ||
var _this2 = this; | ||
@@ -240,5 +352,7 @@ debug('building router'); | ||
var preRegisteredRoutes = {}; | ||
_this.routes.forEach(function (route) { | ||
_this2.routes.forEach(function (route) { | ||
preRegisteredRoutes[route.name] = route; | ||
}); | ||
// Allows selective application of some of the preregistered routes | ||
routes = config.routes.map(function (route) { | ||
@@ -254,5 +368,7 @@ if (typeof route === 'string') { | ||
})(); | ||
} else { | ||
} 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'); | ||
} | ||
@@ -263,3 +379,3 @@ | ||
if (routeNames[route.name]) { | ||
throw 'Route names must be unique'; | ||
throw new Error('Route names must be unique'); | ||
} | ||
@@ -278,3 +394,3 @@ routeNames[route.name] = true; | ||
debug('Found matching route (' + route.name + ') for ' + url); | ||
_this.push(route.name, [url, opts]); | ||
_this2.push(route.name, [url, opts]); | ||
@@ -307,2 +423,10 @@ if (config.responses[route.name]) { | ||
} | ||
/** | ||
* push | ||
* Records history of fetch calls | ||
* @param {String} name Name of the route matched by the call | ||
* @param {Object} call {url, opts} pair | ||
*/ | ||
}, { | ||
@@ -319,87 +443,9 @@ key: 'push', | ||
} | ||
}, { | ||
key: 'mock', | ||
value: function mock(matcher, method, response) { | ||
var config = undefined; | ||
if (response) { | ||
/** | ||
* restore | ||
* Restores global fetch to its initial state and resets call history | ||
*/ | ||
config = { | ||
routes: [{ | ||
matcher: matcher, | ||
method: method, | ||
response: response | ||
}] | ||
}; | ||
} else if (method) { | ||
config = { | ||
routes: [{ | ||
matcher: matcher, | ||
response: method | ||
}] | ||
}; | ||
} else if (matcher instanceof Array) { | ||
config = { | ||
routes: matcher | ||
}; | ||
} else if (matcher && matcher.matcher) { | ||
config = { | ||
routes: [matcher] | ||
}; | ||
} else { | ||
config = matcher; | ||
} | ||
debug('mocking fetch'); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
} | ||
this.isMocking = true; | ||
this._matchedCalls = []; | ||
this._unmatchedCalls = []; | ||
this.mockedContext.fetch = this.constructMock(config); | ||
return this; | ||
} | ||
}, { | ||
key: 'constructMock', | ||
value: function constructMock(config) { | ||
var _this2 = this; | ||
debug('constructing mock function'); | ||
config = config || {}; | ||
var router = this.getRouter(config); | ||
config.greed = config.greed || 'none'; | ||
var mock = function mock(url, opts) { | ||
var response = router(url, opts); | ||
if (response) { | ||
debug('response found for ' + url); | ||
return mockResponse(url, response); | ||
} else { | ||
debug('response not found for ' + url); | ||
_this2.push(null, [url, opts]); | ||
if (config.greed === 'good') { | ||
debug('sending default good response'); | ||
return mockResponse(url, { body: 'unmocked url: ' + url }); | ||
} else if (config.greed === 'bad') { | ||
debug('sending default bad response'); | ||
return mockResponse(url, { throws: 'unmocked url: ' + url }); | ||
} else { | ||
debug('forwarding to default fetch'); | ||
return _this2.realFetch(url, opts); | ||
} | ||
} | ||
}; | ||
mock.augment = function (routes) { | ||
router.augment(routes); | ||
}; | ||
return mock; | ||
} | ||
}, { | ||
key: 'restore', | ||
@@ -413,2 +459,9 @@ value: function restore() { | ||
} | ||
/** | ||
* reMock | ||
* Same as .mock(), but also calls .restore() internally | ||
* @return {FetchMock} Returns the FetchMock instance, so can be chained | ||
*/ | ||
}, { | ||
@@ -420,2 +473,9 @@ key: 'reMock', | ||
} | ||
/** | ||
* getMock | ||
* Returns a reference to the stub function used to mock fetch | ||
* @return {Function} | ||
*/ | ||
}, { | ||
@@ -426,2 +486,8 @@ key: 'getMock', | ||
} | ||
/** | ||
* reset | ||
* Resets call history | ||
*/ | ||
}, { | ||
@@ -435,2 +501,8 @@ key: 'reset', | ||
} | ||
/** | ||
* calls | ||
* Returns call history. See README | ||
*/ | ||
}, { | ||
@@ -444,2 +516,8 @@ key: 'calls', | ||
} | ||
/** | ||
* called | ||
* Returns whether fetch has been called matching a configured route. See README | ||
*/ | ||
}, { | ||
@@ -453,2 +531,62 @@ key: 'called', | ||
} | ||
/** | ||
* 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; | ||
}); | ||
} | ||
}]); | ||
@@ -455,0 +593,0 @@ |
{ | ||
"name": "fetch-mock", | ||
"version": "3.2.0", | ||
"version": "3.2.1", | ||
"description": "Mock http requests made using fetch (or isomorphic-fetch)", | ||
@@ -40,2 +40,3 @@ "main": "src/server.js", | ||
"chai": "^2.3.0", | ||
"eslint": "^1.10.3", | ||
"karma": "^0.12.31", | ||
@@ -42,0 +43,0 @@ "karma-browserify": "^4.1.2", |
@@ -38,3 +38,3 @@ # fetch-mock [![Build Status](https://travis-ci.org/wheresrhys/fetch-mock.svg?branch=master)](https://travis-ci.org/wheresrhys/fetch-mock) | ||
#### `reMock()` | ||
Calls `restore()` internally then calls `mock()`. This allows you to put some generic calls to `mock()` in a `beforeEach()` while retaining the flexibility to vary the responses for some tests | ||
Calls `restore()` internally then calls `mock()`. This allows you to put some generic calls to `mock()` in a `beforeEach()` while retaining the flexibility to vary the responses for some tests. `reMock()` can be chained. | ||
@@ -85,3 +85,4 @@ #### `reset()` | ||
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) | ||
* `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. | ||
@@ -88,0 +89,0 @@ ``` |
@@ -10,4 +10,3 @@ 'use strict'; | ||
Headers: window.Headers, | ||
Blob: window.Blob, | ||
debug: function () {} | ||
}); |
@@ -7,9 +7,20 @@ 'use strict'; | ||
let stream; | ||
let Blob; | ||
let theGlobal; | ||
let debug; | ||
/** | ||
* mockResponse | ||
* Constructs a Response object to return from the mocked fetch | ||
* @param {String} url url parameter fetch was called with | ||
* @param {Object} config configuration for the response to be constructed | ||
* @return {Promise} Promise for a Response object (or a rejected response to imitate network failure) | ||
*/ | ||
function mockResponse (url, config) { | ||
debug('mocking response for ' + url); | ||
// allow just body to be passed in as this is the commonest use case | ||
if (config.throws) { | ||
debug('mocking failed request for ' + url); | ||
return Promise.reject(config.throws); | ||
} | ||
if (typeof config === 'number') { | ||
@@ -29,17 +40,13 @@ debug('status response detected for ' + url); | ||
if (config.throws) { | ||
debug('mocking failed request for ' + url); | ||
return Promise.reject(config.throws); | ||
} | ||
const opts = config.opts || {}; | ||
opts.url = url; | ||
opts.status = config.status || 200; | ||
// the ternary oprator is to cope with new Headers(undefined) throwing in chrome | ||
// (unclear to me if this is a bug or if the specification says this is correct behaviour) | ||
// The ternary operator is to cope with new Headers(undefined) throwing in Chrome | ||
// https://code.google.com/p/chromium/issues/detail?id=335871 | ||
opts.headers = config.headers ? new Headers(config.headers) : new Headers(); | ||
let body = config.body; | ||
/*eslint-disable*/ | ||
if (config.body != null && typeof body === 'object') { | ||
/*eslint-enable*/ | ||
body = JSON.stringify(body); | ||
@@ -52,3 +59,5 @@ } | ||
let s = new stream.Readable(); | ||
/*eslint-disable*/ | ||
if (body != null) { | ||
/*eslint-enable*/ | ||
s.push(body, 'utf-8'); | ||
@@ -63,3 +72,11 @@ } | ||
function normalizeRequest(url, options) { | ||
/** | ||
* normalizeRequest | ||
* Given the parameters fetch was called with, normalises Request or url + options pairs | ||
* to a standard container object passed to matcher functions | ||
* @param {String|Request} url | ||
* @param {Object} options | ||
* @return {Object} {url, method} | ||
*/ | ||
function normalizeRequest (url, options) { | ||
if (Request.prototype.isPrototypeOf(url)) { | ||
@@ -78,20 +95,19 @@ return { | ||
/** | ||
* compileRoute | ||
* Given a route configuration object, validates the object structure and compiles | ||
* the object into a {name, matcher, response} triple | ||
* @param {Object} route route config | ||
* @return {Object} {name, matcher, response} | ||
*/ | ||
function compileRoute (route) { | ||
var method = route.method; | ||
var matchMethod; | ||
if(method) { | ||
method = method.toLowerCase(); | ||
matchMethod = function(req) { | ||
var m = req && req.method ? req.method.toLowerCase() : 'get'; | ||
return m === method; | ||
}; | ||
} else { | ||
matchMethod = function(){ return true; }; | ||
} | ||
debug('compiling route: ' + route.name); | ||
if (typeof route.response === 'undefined') { | ||
throw new Error('Each route must define a response'); | ||
} | ||
if (!route.matcher) { | ||
throw 'each route must specify a string, regex or function to match calls to fetch'; | ||
throw new Error('each route must specify a string, regex or function to match calls to fetch'); | ||
} | ||
@@ -104,20 +120,29 @@ | ||
if (typeof route.response === 'undefined') { | ||
throw 'each route must define a response'; | ||
// If user has provided a function as a matcher we assume they are handling all the | ||
// matching logic they need | ||
if (typeof route.matcher === 'function') { | ||
return route; | ||
} | ||
const expectedMethod = route.method && route.method.toLowerCase(); | ||
function matchMethod (method) { | ||
return !expectedMethod || expectedMethod === (method ? method.toLowerCase() : 'get'); | ||
}; | ||
let matchUrl; | ||
if (typeof route.matcher === 'string') { | ||
let expectedUrl = route.matcher; | ||
if (route.matcher.indexOf('^') === 0) { | ||
debug('constructing starts with string matcher for route: ' + route.name); | ||
expectedUrl = expectedUrl.substr(1); | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && req.url.indexOf(expectedUrl) === 0; | ||
const expectedUrl = route.matcher.substr(1); | ||
matchUrl = function (url) { | ||
return url.indexOf(expectedUrl) === 0; | ||
}; | ||
} else { | ||
debug('constructing string matcher for route: ' + route.name); | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && req.url === expectedUrl; | ||
const expectedUrl = route.matcher; | ||
matchUrl = function (url) { | ||
return url === expectedUrl; | ||
}; | ||
@@ -128,11 +153,22 @@ } | ||
const urlRX = route.matcher; | ||
route.matcher = function (url, options) { | ||
var req = normalizeRequest(url, options); | ||
return matchMethod(req) && urlRX.test(req.url); | ||
matchUrl = function (url) { | ||
return urlRX.test(url); | ||
}; | ||
} | ||
route.matcher = function (url, options) { | ||
const req = normalizeRequest(url, options); | ||
return matchMethod(req.method) && matchUrl(req.url); | ||
}; | ||
return route; | ||
} | ||
class FetchMock { | ||
/** | ||
* constructor | ||
* Sets up scoped references to configuration passed in from client/server bootstrappers | ||
* @param {Object} opts | ||
*/ | ||
constructor (opts) { | ||
@@ -143,3 +179,2 @@ Headers = opts.Headers; | ||
stream = opts.stream; | ||
Blob = opts.Blob; | ||
theGlobal = opts.theGlobal; | ||
@@ -149,2 +184,4 @@ debug = opts.debug; | ||
this._calls = {}; | ||
this._matchedCalls = []; | ||
this._unmatchedCalls = []; | ||
this.mockedContext = theGlobal; | ||
@@ -154,2 +191,7 @@ this.realFetch = theGlobal.fetch && theGlobal.fetch.bind(theGlobal); | ||
/** | ||
* useNonGlobalFetch | ||
* Sets fetchMock's default internal reference to native fetch to the given function | ||
* @param {Function} func | ||
*/ | ||
useNonGlobalFetch (func) { | ||
@@ -160,45 +202,108 @@ this.mockedContext = this; | ||
registerRoute (name, matcher, response) { | ||
debug('registering routes'); | ||
let routes; | ||
if (name instanceof Array) { | ||
routes = name; | ||
} else if (arguments.length === 3 ) { | ||
routes = [{ | ||
name, | ||
matcher, | ||
response, | ||
}]; | ||
/** | ||
* mock | ||
* Replaces fetch with a stub which attempts to match calls against configured routes | ||
* See README for details of parameters | ||
* @return {FetchMock} Returns the FetchMock instance, so can be chained | ||
*/ | ||
mock (matcher, method, response) { | ||
let config; | ||
// Handle the variety of parameters accepted by mock (see README) | ||
if (response) { | ||
config = { | ||
routes: [{ | ||
matcher, | ||
method, | ||
response | ||
}] | ||
} | ||
} else if (method) { | ||
config = { | ||
routes: [{ | ||
matcher, | ||
response: method | ||
}] | ||
} | ||
} else if (matcher instanceof Array) { | ||
config = { | ||
routes: matcher | ||
} | ||
} else if (matcher && matcher.matcher) { | ||
config = { | ||
routes: [matcher] | ||
} | ||
} else { | ||
routes = [name]; | ||
config = matcher; | ||
} | ||
debug('registering routes: ' + routes.map(r => r.name)); | ||
debug('mocking fetch'); | ||
this.routes = this.routes.concat(routes.map(compileRoute)); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
} | ||
this.isMocking = true; | ||
this.mockedContext.fetch = this.constructMock(config); | ||
return this; | ||
} | ||
unregisterRoute (names) { | ||
/** | ||
* constructMock | ||
* Constructs a function which attempts to match fetch calls against routes (see constructRouter) | ||
* and handles success or failure of that attempt accordingly | ||
* @param {Object} config See README | ||
* @return {Function} Function expecting url + options or a Request object, and returning | ||
* 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 | ||
*/ | ||
constructMock (config) { | ||
debug('constructing mock function'); | ||
config = config || {}; | ||
const router = this.constructRouter(config); | ||
config.greed = config.greed || 'none'; | ||
if (!names) { | ||
debug('unregistering all routes'); | ||
this.routes = []; | ||
return; | ||
const mock = (url, opts) => { | ||
const response = router(url, opts); | ||
if (response) { | ||
debug('response found for ' + url); | ||
return mockResponse(url, response); | ||
} else { | ||
debug('response not found for ' + url); | ||
this.push(null, [url, opts]); | ||
if (config.greed === 'good') { | ||
debug('sending default good response'); | ||
return mockResponse(url, {body: 'unmocked url: ' + url}); | ||
} else if (config.greed === 'bad') { | ||
debug('sending default bad response'); | ||
return mockResponse(url, {throws: 'unmocked url: ' + url}); | ||
} else { | ||
debug('forwarding to default fetch'); | ||
return this.realFetch(url, opts); | ||
} | ||
} | ||
}; | ||
mock.augment = function (routes) { | ||
router.augment(routes); | ||
} | ||
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; | ||
}); | ||
return mock; | ||
} | ||
getRouter (config) { | ||
/** | ||
* 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 | ||
*/ | ||
constructRouter (config) { | ||
debug('building router'); | ||
@@ -218,2 +323,4 @@ | ||
}); | ||
// Allows selective application of some of the preregistered routes | ||
routes = config.routes.map(route => { | ||
@@ -228,5 +335,7 @@ if (typeof route === 'string') { | ||
}); | ||
} else { | ||
} 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') | ||
} | ||
@@ -238,3 +347,3 @@ | ||
if (routeNames[route.name]) { | ||
throw 'Route names must be unique'; | ||
throw new Error('Route names must be unique'); | ||
} | ||
@@ -282,2 +391,8 @@ routeNames[route.name] = true; | ||
/** | ||
* push | ||
* Records history of fetch calls | ||
* @param {String} name Name of the route matched by the call | ||
* @param {Object} call {url, opts} pair | ||
*/ | ||
push (name, call) { | ||
@@ -293,84 +408,6 @@ if (name) { | ||
mock (matcher, method, response) { | ||
let config; | ||
if (response) { | ||
config = { | ||
routes: [{ | ||
matcher, | ||
method, | ||
response | ||
}] | ||
} | ||
} else if (method) { | ||
config = { | ||
routes: [{ | ||
matcher, | ||
response: method | ||
}] | ||
} | ||
} else if (matcher instanceof Array) { | ||
config = { | ||
routes: matcher | ||
} | ||
} else if (matcher && matcher.matcher) { | ||
config = { | ||
routes: [matcher] | ||
} | ||
} else { | ||
config = matcher; | ||
} | ||
debug('mocking fetch'); | ||
if (this.isMocking) { | ||
this.mockedContext.fetch.augment(config.routes); | ||
return this; | ||
} | ||
this.isMocking = true; | ||
this._matchedCalls = []; | ||
this._unmatchedCalls = []; | ||
this.mockedContext.fetch = this.constructMock(config); | ||
return this; | ||
} | ||
constructMock (config) { | ||
debug('constructing mock function'); | ||
config = config || {}; | ||
const router = this.getRouter(config); | ||
config.greed = config.greed || 'none'; | ||
const mock = (url, opts) => { | ||
const response = router(url, opts); | ||
if (response) { | ||
debug('response found for ' + url); | ||
return mockResponse(url, response); | ||
} else { | ||
debug('response not found for ' + url); | ||
this.push(null, [url, opts]); | ||
if (config.greed === 'good') { | ||
debug('sending default good response'); | ||
return mockResponse(url, {body: 'unmocked url: ' + url}); | ||
} else if (config.greed === 'bad') { | ||
debug('sending default bad response'); | ||
return mockResponse(url, {throws: 'unmocked url: ' + url}); | ||
} else { | ||
debug('forwarding to default fetch'); | ||
return this.realFetch(url, opts); | ||
} | ||
} | ||
}; | ||
mock.augment = function (routes) { | ||
router.augment(routes); | ||
} | ||
return mock; | ||
} | ||
/** | ||
* restore | ||
* Restores global fetch to its initial state and resets call history | ||
*/ | ||
restore () { | ||
@@ -384,2 +421,7 @@ debug('restoring fetch'); | ||
/** | ||
* reMock | ||
* Same as .mock(), but also calls .restore() internally | ||
* @return {FetchMock} Returns the FetchMock instance, so can be chained | ||
*/ | ||
reMock () { | ||
@@ -390,2 +432,7 @@ this.restore(); | ||
/** | ||
* getMock | ||
* Returns a reference to the stub function used to mock fetch | ||
* @return {Function} | ||
*/ | ||
getMock () { | ||
@@ -395,2 +442,6 @@ return this.fetch; | ||
/** | ||
* reset | ||
* Resets call history | ||
*/ | ||
reset () { | ||
@@ -403,2 +454,6 @@ debug('resetting call logs'); | ||
/** | ||
* calls | ||
* Returns call history. See README | ||
*/ | ||
calls (name) { | ||
@@ -411,2 +466,6 @@ return name ? (this._calls[name] || []) : { | ||
/** | ||
* called | ||
* Returns whether fetch has been called matching a configured route. See README | ||
*/ | ||
called (name) { | ||
@@ -418,4 +477,56 @@ if (!name) { | ||
} | ||
/** | ||
* 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; |
@@ -5,4 +5,4 @@ 'use strict'; | ||
var fetchMock = require('../src/client.js'); | ||
var expect = require('chai').expect; | ||
const fetchMock = require('../src/client.js'); | ||
const expect = require('chai').expect; | ||
@@ -12,3 +12,3 @@ describe('native fetch behaviour', function () { | ||
it('should not throw when passing unmatched calls through to native fetch', function () { | ||
fetchMock.mock(); | ||
fetchMock.mock(/a/, 200); | ||
expect(function () { | ||
@@ -15,0 +15,0 @@ fetch('http://www.example.com'); |
'use strict'; | ||
var fetchMock = require('../src/server.js'); | ||
var fetchCalls = []; | ||
var expect = require('chai').expect; | ||
var sinon = require('sinon'); | ||
const fetchMock = require('../src/server.js'); | ||
const fetchCalls = []; | ||
const expect = require('chai').expect; | ||
@@ -11,3 +10,3 @@ // we can't use sinon to spy on fetch in these tests as fetch-mock | ||
// stubbed function, so just use this very basic stub | ||
var dummyFetch = function () { | ||
const dummyFetch = function () { | ||
fetchCalls.push([].slice.call(arguments)); | ||
@@ -17,6 +16,2 @@ return Promise.resolve(arguments); | ||
var err = function (err) { | ||
console.log(error); | ||
} | ||
require('./spec')(fetchMock, GLOBAL, require('node-fetch').Request); | ||
@@ -35,3 +30,3 @@ | ||
expect(fetchMock.realFetch).to.equal(dummyFetch); | ||
var mock = fetchMock.mock().getMock(); | ||
const mock = fetchMock.mock().getMock(); | ||
expect(typeof mock).to.equal('function'); | ||
@@ -38,0 +33,0 @@ expect(function () { |
'use strict'; | ||
var expect = require('chai').expect; | ||
var err = function (err) { | ||
console.log(error); | ||
} | ||
const expect = require('chai').expect; | ||
module.exports = function (fetchMock, theGlobal, Request) { | ||
@@ -10,4 +7,9 @@ | ||
var fetchCalls = []; | ||
var dummyFetch = function () { | ||
let fetchCalls = []; | ||
const dummyRoute = { | ||
matcher: /a/, | ||
response: 200 | ||
}; | ||
const dummyFetch = function () { | ||
fetchCalls.push([].slice.call(arguments)); | ||
@@ -28,3 +30,3 @@ return Promise.resolve(arguments); | ||
it('call fetch if no routes defined', function () { | ||
fetchMock.mock(); | ||
fetchMock.mock(dummyRoute); | ||
fetch('url', {prop: 'val'}); | ||
@@ -37,3 +39,3 @@ expect(fetchCalls.length).to.equal(1); | ||
it('restores fetch', function () { | ||
fetchMock.mock(); | ||
fetchMock.mock(dummyRoute); | ||
fetchMock.restore(); | ||
@@ -67,6 +69,6 @@ expect(fetch).to.equal(dummyFetch); | ||
it('allow remocking after being restored', function () { | ||
fetchMock.mock(); | ||
fetchMock.mock(dummyRoute); | ||
fetchMock.restore(); | ||
expect(function () { | ||
fetchMock.mock(); | ||
fetchMock.mock(dummyRoute); | ||
fetchMock.restore(); | ||
@@ -203,3 +205,3 @@ }).not.to.throw(); | ||
it('record history of unmatched routes', function (done) { | ||
fetchMock.mock(); | ||
fetchMock.mock(dummyRoute); | ||
Promise.all([ | ||
@@ -211,3 +213,3 @@ fetch('http://1', {method: 'GET'}), | ||
expect(fetchMock.called()).to.be.false; | ||
var unmatchedCalls = fetchMock.calls().unmatched; | ||
const unmatchedCalls = fetchMock.calls().unmatched; | ||
expect(unmatchedCalls.length).to.equal(2); | ||
@@ -222,3 +224,3 @@ expect(unmatchedCalls[0]).to.eql(['http://1', {method: 'GET'}]); | ||
it('configure to send good responses', function (done) { | ||
fetchMock.mock({greed: 'good'}); | ||
fetchMock.mock({routes: dummyRoute, greed: 'good'}); | ||
fetch('http://1') | ||
@@ -237,3 +239,3 @@ .then(function (res) { | ||
it('configure to send bad responses', function (done) { | ||
fetchMock.mock({greed: 'bad'}); | ||
fetchMock.mock({routes: dummyRoute, greed: 'bad'}); | ||
fetch('http://1') | ||
@@ -248,3 +250,3 @@ .catch(function (res) { | ||
it('configure to pass through to native fetch', function (done) { | ||
fetchMock.mock({greed: 'none'}); | ||
fetchMock.mock({routes: dummyRoute, greed: 'none'}); | ||
fetch('http://1') | ||
@@ -273,3 +275,3 @@ .then(function () { | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.thereabouts')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -294,3 +296,3 @@ expect(fetchMock.called('route')).to.be.true; | ||
fetch('/it.at.there/', {method: 'POST'}) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -314,3 +316,3 @@ expect(fetchMock.called('route')).to.be.true; | ||
fetch(new Request('http://it.at.there/', {method: 'POST'})) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -337,3 +339,3 @@ expect(fetchMock.called('route')).to.be.true; | ||
) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -357,3 +359,3 @@ expect(fetchMock.called('route')).to.be.true; | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.there/12345'), fetch('http://it.at.there/abcde')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -383,3 +385,3 @@ expect(fetchMock.called('route')).to.be.true; | ||
]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -394,3 +396,3 @@ expect(fetchMock.called('route')).to.be.true; | ||
it('match method', function(done) { | ||
it('match method', function (done) { | ||
fetchMock.mock({ | ||
@@ -410,3 +412,3 @@ routes: [{ | ||
Promise.all([fetch('http://it.at.here/', {method: 'put'}), fetch('http://it.at.here/'), fetch('http://it.at.here/', {method: 'GET'}), fetch('http://it.at.here/', {method: 'delete'})]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -436,3 +438,3 @@ expect(fetchMock.called('route1')).to.be.true; | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/'), fetch('http://it.at.nowhere')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -462,3 +464,3 @@ expect(fetchMock.called('route1')).to.be.true; | ||
Promise.all([fetch('http://it.at.there/')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -482,3 +484,3 @@ expect(fetchMock.called('route1')).to.be.true; | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.thereabouts', {headers: {head: 'val'}})]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.called()).to.be.true; | ||
@@ -502,3 +504,3 @@ expect(fetchMock.called('route')).to.be.true; | ||
fetch('http://it.at.there/') | ||
.then(function (res) { | ||
.then(function () { | ||
fetchMock.reset(); | ||
@@ -522,3 +524,3 @@ expect(fetchMock.called()).to.be.false; | ||
fetch('http://it.at.there/') | ||
.then(function (res) { | ||
.then(function () { | ||
fetchMock.restore(); | ||
@@ -600,4 +602,3 @@ expect(fetchMock.called()).to.be.false; | ||
done(); | ||
}) | ||
.catch(err); | ||
}); | ||
}); | ||
@@ -718,3 +719,3 @@ | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.calls('route1').length).to.equal(1); | ||
@@ -742,3 +743,3 @@ expect(fetchMock.calls('route2').length).to.equal(1); | ||
it('register routes multiple times', function () { | ||
it('register routes multiple times', function (done) { | ||
fetchMock.registerRoute('route1', 'http://it.at.there/', 'a string'); | ||
@@ -748,3 +749,3 @@ fetchMock.registerRoute('route2', 'http://it.at.here/', 'a string'); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.calls('route1').length).to.equal(1); | ||
@@ -774,3 +775,3 @@ expect(fetchMock.calls('route2').length).to.equal(1); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.calls('route1').length).to.equal(1); | ||
@@ -801,3 +802,3 @@ expect(fetchMock.calls('route2').length).to.equal(0); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/'), fetch('http://it.at.where/')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.calls('route3').length).to.equal(1); | ||
@@ -848,3 +849,3 @@ expect(fetchMock.calls('route1').length).to.equal(0); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/'), fetch('http://it.at.where/')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.calls('route3').length).to.equal(1); | ||
@@ -873,3 +874,3 @@ expect(fetchMock.calls('route1').length).to.equal(1); | ||
Promise.all([fetch('http://it.at.there/'), fetch('http://it.at.here/')]) | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.calls('route2').length).to.equal(1); | ||
@@ -896,3 +897,3 @@ expect(fetchMock.calls('route1').length).to.equal(1); | ||
fetch('http://it.at.here/') | ||
.then(function (res) { | ||
.then(function () { | ||
expect(fetchMock.calls('route2').length).to.equal(1); | ||
@@ -899,0 +900,0 @@ expect(fetchMock.calls('route1').length).to.equal(0); |
Sorry, the diff of this file is not supported yet
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
91231
18
2402
294
17