UNPKG

fetch-mock

Version:

Mock http requests made using fetch (or isomorphic-fetch)

386 lines (338 loc) 10.2 kB
'use strict'; var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; } var Headers = undefined; var Response = undefined; var stream = undefined; var Blob = undefined; var theGlobal = undefined; var debug = undefined; function mockResponse(url, config) { debug('mocking response for ' + url); // allow just body to be passed in as this is the commonest use case if (typeof config === 'number') { debug('status response detected for ' + url); config = { status: config }; } else if (typeof config === 'string' || !(config.body || config.headers || config.throws || config.status)) { debug('body response detected for ' + url); config = { body: config }; } else { debug('full config 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) opts.headers = config.headers ? new Headers(config.headers) : new Headers(); var body = config.body; if (config.body != null && (typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') { body = JSON.stringify(body); } debug('sending body "' + body + '"" for ' + url); if (stream) { var s = new stream.Readable(); if (body != null) { s.push(body, 'utf-8'); } s.push(null); body = s; } return Promise.resolve(new Response(body, opts)); } function compileRoute(route) { var method = route.method; var matchMethod; if (method) { method = method.toLowerCase(); matchMethod = function (options) { var m = options && options.method ? options.method.toLowerCase() : 'get'; return m === method; }; } else { matchMethod = function () { return true; }; } debug('compiling route: ' + route.name); if (!route.name) { throw 'each route must be named'; } if (!route.matcher) { throw 'each route must specify a string, regex or function to match calls to fetch'; } if (typeof route.response === 'undefined') { throw 'each route must define a response'; } if (typeof route.matcher === 'string') { (function () { var 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) { return matchMethod(options) && url.indexOf(expectedUrl) === 0; }; } else { debug('constructing string matcher for route: ' + route.name); route.matcher = function (url, options) { return matchMethod(options) && url === expectedUrl; }; } })(); } else if (route.matcher instanceof RegExp) { (function () { debug('constructing regex matcher for route: ' + route.name); var urlRX = route.matcher; route.matcher = function (url, options) { return matchMethod(options) && urlRX.test(url); }; })(); } return route; } var FetchMock = (function () { function FetchMock(opts) { _classCallCheck(this, FetchMock); Headers = opts.Headers; Response = opts.Response; stream = opts.stream; Blob = opts.Blob; theGlobal = opts.theGlobal; debug = opts.debug; this.routes = []; this._calls = {}; this.mockedContext = theGlobal; this.realFetch = theGlobal.fetch; } _createClass(FetchMock, [{ key: 'useNonGlobalFetch', value: function useNonGlobalFetch(func) { this.mockedContext = this; this.realFetch = func; } }, { 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)); } }, { 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; }); } }, { key: 'getRouter', value: function getRouter(config) { var _this = this; debug('building router'); var routes = undefined; if (config.routes) { (function () { debug('applying one time only routes'); if (!(config.routes instanceof Array)) { config.routes = [config.routes]; } var preRegisteredRoutes = {}; _this.routes.forEach(function (route) { preRegisteredRoutes[route.name] = route; }); 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 { debug('no one time only routes defined. Using preregistered routes only'); routes = this.routes; } var routeNames = {}; routes.forEach(function (route) { if (routeNames[route.name]) { throw 'Route names must be unique'; } routeNames[route.name] = true; }); config.responses = config.responses || {}; return function (url, opts) { var response = undefined; debug('searching for matching route for ' + url); routes.some(function (route) { if (route.matcher(url, opts)) { debug('Found matching route (' + route.name + ') for ' + url); _this.push(route.name, [url, opts]); 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; }; } }, { key: 'push', value: function push(name, call) { this._calls[name] = this._calls[name] || []; this._calls[name].push(call); } }, { key: 'mock', value: function mock(matcher, method, response) { var config = undefined; if (response) { config = { routes: [{ name: '_mock', matcher: matcher, method: method, response: response }] }; } else if (method) { config = { routes: [{ name: '_mock', 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) { throw 'fetch-mock is already mocking routes. Call .restore() before mocking again or use .reMock() if this is intentional'; } this.isMocking = true; return this.mockedContext.fetch = this.constructMock(config); } }, { 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'; return function (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('__unmatched', [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); } } }; } }, { key: 'restore', value: function restore() { debug('restoring fetch'); this.isMocking = false; this.mockedContext.fetch = this.realFetch; this.reset(); debug('fetch restored'); } }, { key: 'reMock', value: function reMock() { this.restore(); this.mock.apply(this, [].slice.apply(arguments)); } }, { key: 'reset', value: function reset() { debug('resetting call logs'); this._calls = {}; } }, { key: 'calls', value: function calls(name) { return name ? this._calls[name] || [] : this._calls._mock || this._calls; } }, { key: 'called', value: function called(name) { if (!name) { return !!Object.keys(this._calls).length; } return !!(this._calls[name] && this._calls[name].length); } }]); return FetchMock; })(); module.exports = FetchMock;