Mocks network requests with desired static and dynamic responses automatically so you never have to change your source code to use mocks ever again.
- Features
- Installation
- Usage
- Examples
- Separating mocks from source code
- MockRequests API
- Implementation notes
- License
This library was made for the purpose of allowing developers to be able to continue to write code as normal even when their APIs are down, haven't been developed by back-end teams yet, or don't have internet at all. It provides a quick, single point of entry that can be called once and will work throughout your entire app.
In particular, most other network-mocking libraries are not user friendly in that they either:
- Force users to re-write their source code to use mocks and then change it back later in order to use real network requests, or
- Involve complex setup using local servers and proxies, usually in ways that are app-specific and aren't easily transferable to other projects.
This library differs from the others in that it allows you to continue writing code as normal while still using mock network activity.
Specific benefits provided by this library that aren't offered in others:
- You never have to change your source code. This means no more replacing
fetch()
withPromise.resolve(mockResponse)
, and no changing URLs fromwebsite.com/api
tothird-party-mocks.com/api
. - No painful configuration of complex node servers, proxies, or anything else to host mock data.
This also means no need to change URLs from
website.com/api
tolocalhost/api
. - Full support for use along with third-party libraries, including Axios and jest, so they function as normal while still giving you the mocks you want.
- Dynamically update mock responses based on request payloads, previous mock responses, and query parameters in order to mimic back-end alterations of data.
- Query parameter parsing so you can mock all URLs with the same pathname using the same dynamic response function.
- Customizable mock response delays to mimic natural network resolution times.
- Mix mocks with real API calls so you can use both functional and non-functional endpoints simultaneously.
- Simple configuration to split mock code from production code and/or activate mocks via terminal (e.g.
MOCK=true npm start
). - No confusing interfaces or multi-step procedures to getting started. Simply configure it once and you're good to go. This becomes extremely useful if you want to switch app-wide mocks between different logins when developing.
- Greatly simplify API testing. Just define
fetch
and/orXMLHttpRequest
in a test setup file and configureMockRequests
with the responses you expect. It will handle all the heavy-lifting of mocking network responses for you so you don't have to repetitively use e.g.fetch = jest.fn()
. See an example of the one-and-done configuration and testing in the demo. - Compatible with all JavaScript environments, including back-end Node scripts, as long as either
fetch
orXMLHttpRequest
are defined and used in that environment (natively or by polyfill).
-
Using npm (see the npm package):
npm install --save-dev mock-requests
-
Using git:
-
Via npm:
npm install --save-dev https://github.com/D-Pow/mock-requests.git
-
With locally installed repo:
git clone https://github.com/D-Pow/mock-requests.git
package.json:
"mock-requests": "file:<pathToCloneLocation>/mock-requests
-
API docs can be viewed in the JSDoc along with a live demo.
This library wraps XMLHttpRequest
and fetch
such that any network request to a configured
URL will return the specified mock response instead of actually making the network requests.
Otherwise, if a URL hasn't been configured with a mock response, the standard async request is made.
MockRequests
was designed to be used in such a way that wherever you configure it, the entire app
experiences the effects. This means you could configure it in one file and then all other files
that make network requests to the configured URLs will receive the mock responses instead,
even without importing MockRequests
. This makes it very easy to work on the front-end even if
some APIs are down, haven't been developed yet, or if you have no internet access at all.
TL;DR: It is highly recommended to use isomorphic-fetch for any back-end/NodeJS scripts since it "just works" throughout your entire app just like MockRequests does.
Network requests in general
MockRequests generally works with any third-party library because it doesn't alter the library itself, it only changes how fetch
/XMLHttpRequest
work. As such, jest
, axios
, etc. aren't affected since they only provide wrappers around the above without changing how they work.
However, MockRequests relies on those network functions being defined globally before being imported. So, if using a library that modifies those functions/objects, like node-fetch
does, you must heed their warnings to add fetch
, Headers
, etc. as global variables before importing/require
-ing MockRequests. In fact, this is exactly what isomorphic-fetch
does - it imports node-fetch
and then sets all the global variables for you (just like node-fetch
itself recommends) so you don't have to.
In other words, this is the easiest way to make (and mock) network requests:
// [any].mjs
import 'isomorphic-fetch'; // Automatically mocks `fetch()` globally for all files!
import MockRequests from 'mock-requests';
MockRequests.configure({
[apiUrl]: { myKey: 'myVal' },
});
fetch(apiUrl); // Mocked easily and automatically!
as opposed to being forced to call global.fetch()
instead of fetch()
:
// app.mjs
import * as NodeFetch from 'node-fetch';
// Don't import `fetch`/`Headers` individually to avoid polluting the script's namespace.
// Otherwise, you'd have to use `global.fetch(url, options)` so `global.fetch` is used
// rather than the local `fetch` function.
global.fetch = NodeFetch.default;
global.Headers = NodeFetch.Headers;
// Force `global[field] = field` to be set before importing MockRequests
const MockRequests = (await import('mock-requests')).default;
fetch(apiUrl); // Mocked, but cumbersome to setup. Same regardless of MJS or CJS.
// app.cjs equivalent
/* node-fetch@>=3 */
const NodeFetch = await import('node-fetch');
// Same concept as in MJS: Don't pollute the namespace to use `global.fetch` by default
global.fetch = NodeFetch.default;
global.Headers = NodeFetch.Headers;
const MockRequests = require('mock-requests');
// ... mock configuration/network calls
/* node-fetch@<=2 */
global.fetch = require('node-fetch');
global.Headers = fetch.Headers;
const MockRequests = require('mock-requests');
// ... mock configuration/network calls
or, alternatively, being forced to extract the polyfills to a separate file:
// NetworkPolyfill.mjs
import fetch, { Headers } from 'node-fetch';
global.fetch = fetch;
global.Headers = Headers;
// app.mjs
import './NetworkPolyfill.js'; // Must be imported before MockRequests
import MockRequests from 'mock-requests';
MockRequests.configureDynamicResponses(...);
fetch(apiUrl); // Mocked, but requires splitting of network-setup logic to a separate file.
Network requests using Axios in NodeJS
Currently, MockRequests only mocks fetch
and XMLHttpRequest
. When used in NodeJS scripts, Axios attempts using XHR first and falls back to using the NodeJS http
/https
modules if it doesn't exist (source code ref). Thus, an XHR polyfill must be added to use Axios in the live NodeJS code (but not Jest tests, as described in Features).
Furthermore, there is a bug in the NodeJS xmlhttprequest
package caused by them not following the correct WHATWG standard. Until MockRequests adds native support for the NodeJS http
/https
modules, an XHR polyfill library (like the one mentioned here) will have to be used in order to use Axios
in back-end source code. In order to do so, write your code in a similar fashion to that described above:
/* app.mjs */
// Don't pollute namespace by using dynamic imports
global.XMLHttpRequest = (await import('xmlhttprequest')).XMLHttpRequest;
// Force global fields to be defined before defining MockRequests and Axios
const MockRequests = (await import('mock-requests')).default;
const axios = (await import('axios')).default;
// ... your logic
/* app.js */
// First, the polyfill
global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
// Next, MockRequests
const MockRequests = require('mock-requests');
// Finally, Axios
const axios = require('axios');
// ... your logic
Note how in the below examples, the production-bound code doesn't change between mocking and using network calls.
To configure global app usage of MockRequests
, simply call configure()
with an object containing URL-responseObject
mappings.
// This is the only code you need to add to use this library
// Add in the MockConfig.js file described in the "Separating
// mocks from source code" section
import MockRequests from 'mock-requests';
import {
myApiUrl, // 'https://example.com/api/vx/someApi'
anotherUrl // '192.168.0.1'
} from '../src/services/Urls.js';
const myApiMockResponse = { someJson: 'responseObject' };
const anotherUrlMockResponse = '<html>some other type of response</html>';
MockRequests.configure({
[myApiUrl]: myApiMockResponse,
[anotherUrl]: anotherUrlMockResponse
});
// ...source code
// Using your async requests in source code
// This stays the same regardless of if you're using mocks or the actual endpoint
const jsonResponse = await fetch(myApiUrl).then(res => res.json());
const htmlResponse = await fetch(anotherUrl).then(res => res.text());
useResponseContentAsNormal(jsonResponse);
useResponseContentAsNormal(htmlResponse);
Alternatively, you could configure URL-response content individually:
// same URLs and mock responses from above
import MockRequests from 'mock-requests';
MockRequests.setMockUrlResponse(myApiUrl, myApiMockResponse);
MockRequests.setMockUrlResponse(anotherUrl, anotherUrlMockResponse);
In the event that some APIs are not functioning correctly but others are, you can configure
the broken APIs using MockRequests
and then leave the other APIs as-is for normal responses:
import MockRequests from 'mock-requests';
import { nonfunctionalApi } from '../src/services/Urls.js';
MockRequests.setMockUrlResponse(nonfunctionalApi, /* mock response */);
// ...source code
// Will receive mock
const mockedResponse = await fetch(nonfunctionalApi).then(res => res.json());
// Will receive actual API response
const realApiResponse = await fetch(functionalApi).then(res => res.json());
useResponseContent(mockedResponse);
useResponseContent(realApiResponse);
This library also supports dynamically updating your mocked APIs' responses, so as to mimic actual
back-end systems. To utilize this feature, you'll need to call the dynamic counterparts of
configure/setMockUrlResponse
(configureDynamicResponses/setDynamicMockUrlResponse
) along with
a slightly modified config object that has response
and dynamicResponseModFn
fields:
import MockRequests from 'mock-requests';
import { myApiUrl } from '../src/services/Urls.js';
const initialMockResponse = {
data: ['a', 'b', 'c', 'd', 'e'],
value: 7
};
const dynamicConfig1 = {
[myApiUrl]: {
// The desired response is now nested inside the `response` property.
// This will be the initial default value of the `response` parameter
// in the function below, but won't be used after that.
response: initialMockResponse,
// The dynamicResponseModFn takes in the request and previous
// response as arguments to produce the new response.
// The new response **must** be returned from this function.
// Feel free to modify `response` as it will be deep-copied later.
dynamicResponseModFn: (request, response) => {
// Mix both request and response data to generate new response
response.data = response.data.concat(request.addLettersArray);
response.value += request.valueModification;
return response; // is actually now the new response
}
}
};
MockRequests.configureDynamicResponses(dynamicConfig1);
// ...source code
const payload = {
addLettersArray: ['f', 'g'],
valueModification: 5
};
const myDynamicallyModifiedResponse = await fetch(myApiUrl, {
body: JSON.stringify(payload)
}).then(res => res.json());
console.log(myDynamicallyModifiedResponse)
/* Will output:
{
data: ['a', 'b', 'c', 'd', 'e', 'f', 'g'],
value: 12
}
*/
Additionally, the dynamicResponseModFn
will receive an object containing query parameters from the request URL,
which means you also have the option to generate dynamic responses based on those.
If you want to mock all URLs with the same pathname but different query parameters, simply add usePathnameForAllQueries: true
to your dynamic mock configuration.
Regardless of if you set usePathnameForAllQueries
or not, dynamicResponseModFn
will still receive the queryParamMap
.
import {
searchApiPathname // 'https://example.com/search' e.g. search?q=weather
} from '../src/services/Urls.js';
MockRequests.setDynamicMockUrlResponse(searchApiPathname, {
// `response` field not needed because we don't need an initial `response` value
dynamicResponseModFn: (request, response, queryParamMap) => {
const searchQuery = decodeURIComponent(queryParamMap.q);
return `You searched for ${searchQuery}`;
},
usePathnameForAllQueries: true
});
// ...source code
const searchQuery = getSearchFromTextInput(); // let's assume this is `weather`
const searchUrl = `${searchApiPathname}?q=${encodeURIComponent(searchQuery)}`;
const response = await fetch(searchUrl).then(res => res.text());
console.log(response);
/* Will output:
'You searched for weather'
*/
There is also a delay
option you can use if you want to mimic network delays:
// or configureDynamicResponses({ [myApiUrl]: {...} })
MockRequests.setDynamicMockUrlResponse(myApiUrl, {
response: myMockResponse,
dynamicResponseModFn: (req, res, queries) => {/* ... */},
delay: 1500 // will make fetch take 1.5 seconds to resolve myApiUrl
});
By default, MockRequests
mocks XMLHttpRequest
and fetch
such that the response is always valid, setting the corresponding attributes' values as seen below. If you want to change any of these, use the responseProperties
field of the configureDynamicResponses()
/setDynamicMockUrlResponse()
config objects.
For example, set responseProperties.headers
to an object of HTTP header key/value pairs to change the response headers for both XMLHttpRequest
and fetch
, e.g. to change the status code from 200 to 400.
Note: The status(Text)
properties are separate from the status
HTTP header and need to be changed separately.
-
For
XMLHttpRequest
:xhr.readyState = 4; xhr.response = mockedResponse; xhr.responseText = stringVersionOf(mockedResponse); // e.g. JSON.stringify(mockedResponse) xhr.responseUrl = urlPassedInXhrOpenMethod; xhr.status = 200; xhr.statusText = 'OK'; xhr.timeout = 0;
-
For
fetch().then(response => ...)
:response.status = 200; response.statusText = ''; response.ok = true; response.headers = new Headers({ status: '200' }); response.redirected = false; response.type = 'basic';
Finally, because the configure
/setMockUrlResponse
functions take in a simple URL-response mapping,
using different mocks at different times becomes incredibly user-friendly. For example,
if your data changes based on which user is logged in, then the MockRequests
API is
particularly easy to work with. In this case, after defining each user's mock responses,
you could nest them in a single loginMocks
object and simply choose which login to use:
const bobMocks = {
[homepageUrl]: bobHomepageMock,
[friendsUrl]: bobFriendsMock
};
const aliceMocks = {
[homepageUrl]: aliceHomepageMock,
[friendsUrl]: aliceFriendsMock
};
const loginMocks = {
bob: bobMocks,
alice: aliceMocks
};
// Today, I want to be Alice
MockRequests.configure(loginMocks.alice);
// no, wait, I'll be Bob instead
MockRequests.configure(loginMocks.bob);
For convenience, a mapStaticConfigToDynamic()
function has been included to make converting the above
static version of loginMocks
to the dynamic counterpart easier:
// Example 1
// Convert a static URL-response mock to dynamic and add delay to home page
const dynamicBobMocks = MockRequests.mapStaticConfigToDynamic(bobMocks);
dynamicBobMocks[homepageUrl].delay = 1500;
MockRequests.configureDynamicResponses(dynamicBobMocks);
// Example 2
// Convert all loginMocks entries to dynamic counterparts
const dynamicLoginMocks = Object.keys(loginMocks).reduce((dynamicConfigs, user) => {
dynamicConfigs[user] = MockRequests.mapStaticConfigToDynamic(loginMocks[user]);
return dynamicConfigs;
}, {});
MockRequests.configureDynamicResponses(dynamicLoginMocks.bob);
// Example 3
// Merge user-agnostic dynamic mocks with static loginMocks
const dynamicMocks = {
[searchApiPathname]: {
dynamicResponseModFn: (req, res, queries) => {
/* ... same as search query above */
}
}
};
const staticDynamicMerged = Object.keys(loginMocks).reduce((dynamicConfigs, user) => {
dynamicConfigs[user] = {
...MockRequests.mapStaticConfigToDynamic(loginMocks[user]),
...dynamicMocks
};
return dynamicConfigs;
}, {});
MockRequests.configureDynamicResponses(staticDynamicMerged.bob);
In the simplest, bare-bones example, you could just import MockRequests
into one of your entry JavaScript files
(src/index.js, src/App.js, or similar) and configure your mocks there. As long as MockRequests was installed as a
devDependency and you don't commit this code, it will never enter production.
// src/index.js for React project
import React from 'react';
import ReactDOM from 'react-dom';
import App from '/components/App';
import MockRequests from 'mock-requests';
MockRequests.configure(/* ... */);
ReactDOM.render(<App />, document.getElementById('root'));
However, for larger apps with many network calls or for sharing mocks with other team members, typing and removing
mocks can get quite cumbersome. To simplify this, you could simply move mock-related code to a separate mocks/
folder and only import them when needed. This way, you can commit the mock code to your repo but, just like
test code, it doesn't get deployed into production since it isn't in the src/ folder.
For example, if we have the setup:
MyApp
├─── src/
| ├─── (... source code)
├─── mocks/
| ├─── MockConfig.js
| ├─── StaticResponses.js
| ├─── DynamicResponseConfigs.js
| ├─── (... other mock files imported by MockConfig.js)
where MockConfig.js
does all the mock-requests
configuration, e.g.
// mocks/MockConfig.js
import MockRequests from 'mock-requests';
import { myStaticApiUrl, myDynamicApiUrl } from '../src/services/Urls.js';
import { myStaticApiResponse } from './StaticResponses'; // Other files in mocks/
import { myDynamicApiConfig } from './DynamicResponseConfigs';
MockRequests.setMockUrlResponse(myStaticApiUrl, myStaticApiResponse);
MockRequests.setDynamicMockUrlResponse(myDynamicApiUrl, myDynamicApiConfig);
then in your src/index.js file, just import the MockConfig.js file to activate all mocks, and don't commit that code change to keep all of it out of production:
import React from 'react';
import ReactDOM from 'react-dom';
import App from '/components/App';
import '../mocks/MockConfig';
ReactDOM.render(<App />, document.getElementById('root'));
To avoid having to change your source code to activate/deactivate mocks (e.g. src/index.js above), MockRequests
comes with a built-in plugin for projects using webpack. As such, assuming you have a separate directory of mocks and a single mock entry file (see above example), you can simply import the MockRequestsWebpackPlugin
and use via:
// webpack.config.js
const MockRequestsWebpackPlugin = require('mock-requests/bin/MockRequestsWebpackPlugin');
module.exports = {
// ...
plugins: [
// ...
new MockRequestsWebpackPlugin(
'mocks', // Holds all mock-related files imported by the entry file.
// Relative to the webpack "context"/project root (more on this below).
'MockConfig.js', // Mock entry file, nested inside `mocks/`.
process.env.MOCK === 'true' // Whether or not mocks should be activated.
),
// ...
]
};
and run using MOCK=true npm start
.
Use of this plugin will automatically transpile your code (according to your webpack config's JS/TS rules) and activate mocks based on the boolean of whether or not mocks should be activated. This means you never have to change anything in src/
or in webpack.config.js outside of this plugin.
If the boolean condition resolves to false
, then nothing will be added to your build output, keeping mock files out of the final production code. In this example, our toggle is via CLI env variable, but it can be anything else of your choosing.
The webpack plugin comes with a few configuration options to accommodate all types of webpack configurations:
new MockRequestsWebpackPlugin(
'mocks',
'MockConfig.js',
process.env.NODE_ENV === 'development',
{
pathsAreAbsolute, // defaults to `false`
transpileMocksDir // defaults to `true`
}
),
If you prefer using absolute paths instead of relative, then change both the mocksDir
and mockEntryFile
arguments to be absolute, and set pathsAreAbsolute: true
.
If you prefer to nest your mocks/
directory inside src/
or other directory that's already configured to be transpiled, then set transpileMocksDir: false
for added simplicity in webpack processing.
If your project doesn't use webpack or if you prefer to have more control over the file-processing, then you could instead use the resolve-mocks.js
script to generate the paths to the mock directory/entry-file manually.
All you have to do is pass in the same fields from the MockRequestsWebpackPlugin
into the resolveMocks()
function, and spread the resulting entry
/include
arrays where you want them processed. For example:
// webpack.config.js
// Returns an object containing arrays to spread in webpack's `include` and `entry` fields.
// resolveMocks(mockDirectory, mockEntryFile, activateMocksBoolean)
const resolveMocks = require('mock-requests/bin/resolve-mocks');
const resolvedMocks = resolveMocks('mocks', 'mocks/MockConfig.js', process.env.MOCK === 'true');
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
// adds mocks/ directory to loaders for transpilation
include: [ /src/, ...resolvedMocks.include ],
exclude: [ /node_modules/ ],
loader: 'babel-loader'
}
]
},
// adds mocks/MockConfig.js entry file to build output
entry: [ '@babel/polyfill', './src/index.js', ...resolvedMocks.entry ]
}
and run using MOCK=true npm start
.
Doing so will result in the same outcome of the webpack plugin: transpilation of the mocks/
directory so you can write your mocks with the latest JS features, as well as adding the mock entry file to your build/run output dynamically -- all while still being toggled by the CLI. Like the plugin, the mocks won't be added to your build output unless the boolean condition resolves to true
.
In order to make mocking your network calls simpler, config functions have been added to allow for setting, getting, and deleting mock responses for your network calls.
These are described at length in the JSDoc.
- configure(staticUrlResponseConfigObject, overwritePreviousConfig = true)
- configureDynamicResponses(dynamicUrlResponseConfigObject, overwritePreviousConfig = true)
- setMockUrlResponse(url, staticMockResponseObject)
- setDynamicMockUrlResponse(url, dynamicMockResponseObject)
- getResponse(url)
- deleteMockUrlResponse(urlNotMeantToBeMocked)
- clearAllMocks()
- mapStaticConfigToDynamic(staticConfig)
- OriginalXHR()
- originalFetch()
Note that OriginalXHR
and originalFetch
will use the original XMLHttpRequest
and fetch
respectively,
regardless of if you've set the mock URL responses in MockRequests.configure()
or MockRequests.setMockUrlResponse(...)
.
It will also use XMLHttpRequest
and fetch
regardless of if the browser supports them or not (will be undefined
in
cases where the browser doesn't support them).
-
This library also works with other members of the Fetch API, so you can alternatively use an instance of the
Request
class in yourfetch()
calls (e.g.fetch(new Request(url, options))
) or read from theHeaders
instance ofresponse.headers
. -
You may import either the
MockRequests
default export or any of its individual fields, e.g.
import MockRequests, { setMockUrlResponse } from 'mock-requests';
-
This works with any environment that uses either
fetch
orXMLHttpRequest
, regardless of if said environment is a browser, web/service worker, or a NodeJS script. As long asfetch
and/orXMLHttpRequest
are defined globally (whether natively or by polyfill), any network request to a URL configured byMockRequests
will be mocked appropriately. For example:// my-script.js - called via `node my-script.js` require('isomorphic-fetch'); const MockRequests = require('mock-requests'); // ... use fetch and MockRequests as normal