Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
Nock is used to mocking calls to HTTP. It makes it possible for us to specify what URLs to listen to and what to respond with. This is a fairly short article that shows how you use the library nock.
In this article we will cover:
- set up, setting up a nock and specify a mock response
- query parameters, see how we can set up our nock with query parameters
- verification, we should always verify that our nocks were hit. If they weren't then something changed and we need to change our tests to reflect that
Why Mock HTTP?
When we test anything we should do so in isolation, we should focus on only testing the module we are currently in. This means that we should rely on some kind of mocking to external dependencies. Not only for external dependencies but also for anything external like side-effects, something that's outside your control, like HTTP calls. Letting an HTTP call actually go through can have many issues like connectivity and rate limiting and ultimately it tests something outside the scope of your test most likely. The solution to this, for this article, is the library Nock.
Scenario
In the following scenario, we will look at how to test a component that needs to display a list of products. The list data comes from doing an HTTP call.
To be able to run this scenario you should first be creating a React.js project. That is easiest done by running, CRA- Create React App. So let's do that first:
npx create-react-app my-app
Once we have a project let's talk about the files we will need to run our test scenario and testing out Nock.
Imagine we have the following files:
- products.js, a service that can retrieve data for us
- ProductsList.js, a component that calls a method on products.js to get data and render that
Let's have a look at what these two modules look like:
// products.js
export const getProducts = async () => {
const response = await fetch('http://myapi.com/products');
const json = await response.json();
console.log(json);
return json.products;
}
Above we can see that we do a fetch()
call to URL http://myapi.com/products
and thereafter we transform the response and dig out the data products. Let's have a look at the component:
// ProductsList.js
import React from 'react';
import { getProducts } from '../products';
const Products = ({ products }) => (
<React.Fragment>
{products.map(p => <div>{product.name}</div>)}
</React.Fragment>
);
class ProductsContainer extends React.Component {
state = { products: undefined, }
async componentDidMount() {
const products = await getProducts();
this.setState({ products });
}
render() {
if (!this.state.products) return null;
else return ( <Products products={this.state.products} /> );
}
}
export default ProductsContainer;
We can see that we use product.js module and call getProducts()
in the componentDidMount()
and end up rendering the data when it arrives.
Testing it
If we wanted to test ProductsList.js
module we would want to focus on mocking away products.js cause it is a dependency. We could use the library nock for this.
Let's start off by installing nock, like so:
yarn add nock
Let's now create a test __tests__/ProductsList.spec.js
and define it as the following:
// __tests__/ProductsList.spec.js
import React from 'react';
import ReactDOM from 'react-dom';
import ProductsList from '../ProductsList';
import nock from 'nock';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<ProductsList />, div);
ReactDOM.unmountComponentAtNode(div);
});
Let's see first what happens if we don't set up a nock
.
As you can see from the above it attempts to perform a network request. We should never do that when running a test. We could add a Jest
mock for this that is definitely one way to solve it, then it would look like this:
// __mocks__/products.js
export const getProducts = async () => {
const products = await Promise.resolve([{ name: 'test' }]);
return products;
}
That works but let's look at how to solve it with nock. Because we are attempting to call fetch()
in a node environment, we need to ensure it is set up correctly. The suggestion is to set up the global.fetch and assign node-fetch to it, like so:
global.fetch = require('node-fetch');
Let's now add nock to our test, like so:
import React from 'react';
import ReactDOM from 'react-dom';
import ProductsList from '../ProductsList';
import nock from 'nock';
it('renders without crashing', () => {
const scope = nock('http://myapi.com')
.get('/products')
.reply(200, { products: [{ id: 1, name: 'nocked data' }] },
{
'Access-Control-Allow-Origin': '*',
'Content-type': 'application/json'
});
const div = document.createElement('div');
ReactDOM.render(<ProductsList />, div);
ReactDOM.unmountComponentAtNode(div);
});
Note above how we invoke the nock()
method by first giving it the baseUrl
http://myapi.com
followed by the path /products
and the HTTP verb get and how we define what the response should look like with reply()
. We also give the reply()
method a second argument to ensure CORS
plays nicely. At this point our test works:
It's all working and we successfully mocked our HTTP call using nock.
Handle Query parameters using .query()
What if we have a URL that looks like this:
http://myapi.com/products?page=1&pageSize=10
;
How we do we set up our nock to match it? Well, we can use the helper method query for that, like so:
nock('http://myapi.com')
.get('/products')
.query({ page: 1, pageSize: 10 })
Verify your mock/s
It's considered best practice to verify that the mocks you have set up are being hit. To do that we can call done()
on the returned reference when we are calling nock like so:
const scope = nock('http://myapi.com')
.get('/products')
.reply(
200,
{ products: [{ id: 1, name: 'nocked data' }] },
{
'Access-Control-Allow-Origin': '*',
'Content-type': 'application/json'
}
);
scope.done();
So what happens when we set up a mock and it's not being it? Well let's add another call to our test, like so:
const users = nock('http://myapi.com')
.get('/users')
.reply(
200,
{ products: [{ id: 1, name: 'user' }] },
{
'Access-Control-Allow-Origin': '*',
'Content-type': 'application/json'
}
);
Block HTTP calls
You should never let an HTTP call happen for real so therefore make sure to shut off that ability. We can do so by adding the following line to setupTests.js
:
import nock from 'nock';
nock.disableNetConnect();
We have briefly explained what nock is and how to use it for different cases. This is just one way of many to handle HTTP calls.
There are a lot more you can do with nock, we have barely scraped the surface. Have a look at the official documentation Nock documentation
Summary
Ok, we have initially discussed why it's a good idea to intercept any calls made over HTPP. We have mentioned that we can use normal Mocks for this but we have instead chosen the approach over mocking closer to the metal with the library mock. Hopefully, you've seen how easy it is to get started with nock.
Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
Top comments (2)
Great article - does the nock 'mock' only last for the duration of the single test ? e..g
it('renders without crashing', () => {
const scope = nock('myapi.com')
.get('/products')
.reply(200, { products: [{ id: 1, name: 'nocked data' }] },
{
'Access-Control-Allow-Origin': '*',
'Content-type': 'application/json'
});
const div = document.createElement('div');
ReactDOM.render(, div);
ReactDOM.unmountComponentAtNode(div);
});
if I added another test after that would the call to myapi.com/products go through nock without having to set it up?
Thanks
Jojm
It was a while ago since I wrote this..
If my memory serves me correct I believe a nock is considered used as soon as it is hit.
So you need to define a new nock definition... I'm gonna test it to make sure.. Thanks for the question