18. Bonus: how to mock requests in Unit Tests
filipedeschamps opened this issue · comments
Unit tests need to be lighting fast for two reasons:
- There's going to be a lot of them, covering every possible combination
- You can use them to watch the results while you're adding functionalities or refactoring your code
Some things are naturally slow, like making http
requests over the network. This module makes a lot of request since the main feature of it is to keep requesting feed URLs all the time.
There's an amazing lib wrote by @pgte called nock that will intercept those http
requests and respond them instantly with some data you predefined, without making a real http
request over the network.
This is an amazing tool if you really want to isolate and test a unit of your code, not the integration with another service.
Dependency
First thing, install nock
as a development dependency:
$ npm install nock --save-dev
Mocking
Given a known input, what output do we expect? This is what's a mock for. So, you have to build a known input, and for this example, I saved the return of a rss feed from Nintendo Life in this file:
test/unit/fixtures/nintendo-latest-first-fetch.xml
Now we can write tests to see, for example, if our module is correctly returning expected values, for example the title
or description
of a feed without making a real request to the original URL of the feed: http://www.nintendolife.com/feeds/latest
First, let's mock this url inside a new assertion:
it( '"new-item" should contain an Object with at least "title", "description", "summary", "date", "link" and "meta"', ( done ) => {
nock( 'http://www.nintendolife.com/' )
.get( '/feeds/latest' )
.replyWithFile( '200', path.join( __dirname, '/fixtures/nintendo-latest-first-fetch.xml' ) );
} );
- We are telling nock to intercept
http://www.nintendolife.com/
domain - If anything requests the path
/feeds/latest
, respond it with200
status code with the file content we created above
There's a total of 20 feed items in that file, so we will need to keep track of how many itens our feeder will emit using an itemsReceived
array:
it( '"new-item" should contain an Object with at least "title", "description", "summary", "date", "link" and "meta"', ( done ) => {
nock( 'http://www.nintendolife.com/' )
.get( '/feeds/latest' )
.replyWithFile( '200', path.join( __dirname, '/fixtures/nintendo-latest-first-fetch.xml' ) );
let itemsReceived = [];
} );
Now, let's add a feed object to our feed instance:
it( '"new-item" should contain an Object with at least "title", "description", "summary", "date", "link" and "meta"', ( done ) => {
nock( 'http://www.nintendolife.com/' )
.get( '/feeds/latest' )
.replyWithFile( '200', path.join( __dirname, '/fixtures/nintendo-latest-first-fetch.xml' ) );
let itemsReceived = [];
feeder.add( {
url: 'http://www.nintendolife.com/feeds/latest',
refresh: 20000
} );
} );
From now on, we need to listen to those new-item
events and we expect to receive 20
of them
it( '"new-item" should contain an Object with at least "title", "description", "summary", "date", "link" and "meta"', ( done ) => {
nock( 'http://www.nintendolife.com/' )
.get( '/feeds/latest' )
.replyWithFile( '200', path.join( __dirname, '/fixtures/nintendo-latest-first-fetch.xml' ) );
let itemsReceived = [];
feeder.add( {
url: 'http://www.nintendolife.com/feeds/latest',
refresh: 20000
} );
feeder.on( 'new-item', ( item ) => {
let totalLength = 20;
itemsReceived.push( item );
if ( itemsReceived.length === totalLength ) {
done();
}
} );
} );
Not only we need to assert that we received a total of 20 items, but all of them have properties like title
, description
, date
, etc.
it( '"new-item" should contain an Object with at least "title", "description", "summary", "date", "link" and "meta"', ( done ) => {
nock( 'http://www.nintendolife.com/' )
.get( '/feeds/latest' )
.replyWithFile( '200', path.join( __dirname, '/fixtures/nintendo-latest-first-fetch.xml' ) );
let itemsReceived = [];
feeder.add( {
url: 'http://www.nintendolife.com/feeds/latest',
refresh: 20000
} );
feeder.on( 'new-item', ( item ) => {
let totalLength = 20;
itemsReceived.push( item );
expect( item ).to.have.property( 'title' );
expect( item ).to.have.property( 'description' );
expect( item ).to.have.property( 'summary' );
expect( item ).to.have.property( 'date' );
expect( item ).to.have.property( 'link' );
expect( item ).to.have.property( 'meta' );
if ( itemsReceived.length === totalLength ) {
done();
}
} );
} );
Great, you will see that no networks where involved and the results are going to be fast. You can not only mock successful requests, but also returning 404
, 500
status code or even returning an invalid XML to cover the behavior of your module over those situations. It's amazing.