fetch-mock
Version:
Mock http requests made using fetch (or isomorphic-fetch)
250 lines (200 loc) • 11 kB
Markdown
# fetch-mock [](https://travis-ci.org/wheresrhys/fetch-mock) [](https://coveralls.io/r/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.
## Which version to require
- Browser tests: `require('fetch-mock)`
- Server side tests running in nodejs 4 or higher: `require('fetch-mock/server')`
- Server side tests running in nodejs 0.12 or lower: `require('fetch-mock/es5/server)`
You will need to ensure `fetch` and `Promise` are already available as globals in your environment
## API
*To output useful messages for debugging `export DEBUG=fetch-mock`*
**`require('fetch-mock')`** exports a singleton with the following methods
#### `mock()`
Replaces `fetch()` with a stub which records it's calls, grouped by route, and optionally returns a stub response or passes the call through to `fetch()`.
* `mock(matcher, response)` - configuration for a single unnamed route to be mocked (shorthand for `mock(route)` - see below)
* `mock(matcher, method, response)` - configuration for a single unnamed route to be mocked for a given method (shorthand for `mock(route)` - see below)
* `mock(route)` - configuration for a single route with the following properties
* `name` [required]: A unique string naming the route
* `method` [optional]: If specified will only match requests using the given http method
* `matcher` [required]: The rule for matching calls to `fetch()`. Accepts any of the following
* `string`: Either an exact url to match e.g. 'http://www.site.com/page.html' or, if the string begins with a `^`, the string following the `^` must begin the url e.g. '^http://www.site.com' would match 'http://www.site.com' or 'http://www.site.com/page.html'
* `RegExp`: A regular expression to test the url against
* `Function(url, opts)`: A function (returning a Boolean) that is passed the url and opts `fetch()` is called with.
* `response` [required]: Configures the response object returned by the mock. Can take any of the following values
* `number`: Creates a response with this status
* `string`: Creates a 200 response with the string as the response body
* `object`: As long as the object does not contain any of the properties below it is converted into a json string and returned as the body of a 200 response. If any of the properties below are defined it is used to configure a `Response` object
* `body`: Set the response body (`string` or `object`)
* `status`: Set the response status (defaut `200`)
* `headers`: Set the response headers. (`object`)
* `throws`: If this property is present then a `Promise` rejected with the value of `throws` is returned
* `Function(url, opts)`: A function that is passed the url and opts `fetch()` is called with and that returns any of the responses listed above
* `mock(routes)` - array of route configuration objects (see above)
* `mock(config)` - object containing more complex config for fine grained controlled over mocking behaviour with the following properties
- `routes`: Either a single route config object or an array of them (see above)
- `responses`: When `registerRoute()` 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.
```javascript
responses: {
session: function (url, opts) {
if (opts.headers.authorized) {
return {user: 'dummy-authorized-user'};
} else {
return {user: 'dummy-unauthorized-user'};
}
}
}
```
- `greed`: Determines how the mock handles unmatched requests
- 'none': all unmatched calls get passed through to `fetch()`
- 'bad': all unmatched calls result in a rejected promise
- 'good': all unmatched calls result in a resolved promise with a 200 status
#### `restore()`
Restores `fetch()` to its unstubbed state and clears all data recorded for its calls
#### `reMock()`
Normally calling `mock()` twice without restoring inbetween will throw an error. `reMock()` calls `restore()` internally before calling `mock()` again. This allows you to put a generic call to `mock()` in a `beforeEach()` while retaining the flexibility to vary the responses for some tests
#### `reset()`
Clears all data recorded for `fetch()`'s calls
#### `calls(routeName)`
Returns an array of arrays of the arguments passed to `fetch()` that matched the given route. '__unmatched' can be passed in to return results for calls not matching any route.
When shorthands are used by `mock()` to define unnamed routes `routeName` is not required (but `_mock` can be passed in to distinguish from calls to previously registered routes)
#### `called(routeName)`
Returns a Boolean denoting whether any calls matched the given route. '__unmatched' can be passed in to return results for calls not matching any route. If no routeName is passed it returns `true` if any fetch calls were made
#### `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)`
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.
* `func` Optional reference to `fetch` (or any other function you may want to substitute for `fetch` in your tests).
##### Mockery example
```javascript
var fetch = require('node-fetch');
var fetchMock = require('fetch-mock');
var mockery = require('mockery');
fetchMock.useNonGlobalFetch(fetch);
fetchMock.registerRoute([
...
])
it('should make a request', function (done) {
mockery.registerMock('fetch', fetchMock.mock());
// test code goes in here
mockery.deregisterMock('fetch');
done();
});
```
## Examples
```javascript
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().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'
});
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'});
});
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();
})
});
```