filipedeschamps / rss-feed-emitter

Super RSS News Feed aggregator written in Node.js and ES6

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

18. Bonus: how to mock requests in Unit Tests

filipedeschamps opened this issue · comments

Unit tests need to be lighting fast for two reasons:

  1. There's going to be a lot of them, covering every possible combination
  2. 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' ) );

    } );
  1. We are telling nock to intercept http://www.nintendolife.com/ domain
  2. If anything requests the path /feeds/latest, respond it with 200 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.

Next step:

19. Bonus: Commented source code