Jackthomson
Jackthomson

Reputation: 644

Mocha, Sinon and Chai testing two http calls inside a callback

I am doing some really simple testing with Chai Mocha and Sinon. I am wondering how you would go about testing a http method that gets called inside of a callback. Please point me in the right direction if you can, struggling to find anything on it, I know you can do it, just not sure how. My code is below:

index.js

const http = require('http')

class Index {
  add(a, b) {
    return a + b
  }

  get(uri) {
    http.get(uri, () => {
      http.get('/', function() {
        return
      })
    })
  }
}

module.exports = Index

index.spec.js

const Index = require('../index')
const http = require('http')
const { stub, fake, assert } = require('sinon')
const { expect } = require('chai')

let httpSpy;

beforeEach(function () {
  a = new Index()
  httpSpy = stub(http, 'get')
})

describe('When the get method is invoked', function () {
  beforeEach(function () {
    a.get('http://www.google.co.uk')
  })

  it('should make a call to the http service with passed in uri', function () {
    assert.calledOnce(httpSpy) // This really should be called twice (Part I am struggling with)
    assert.calledWith(httpSpy, 'http://www.google.co.uk')
    // I want to test instead that the httpSpy was called twice as, inside the get method, when the first http get resolves, another one gets fired off also
  })
})

Upvotes: 1

Views: 1168

Answers (1)

Antonio Narkevich
Antonio Narkevich

Reputation: 4326

There are two issues.

Firstly, we cannot tell when the Index.get() method execution is ended (it does not accept a callback, neither return a promise, not marked as async etc).

get(uri) { ... }

Usage of such method is critically inconvenient. For example: if we'd want to first do Index.get() and then do some action right after we won't be able to.

To fix the this we can just add a callback as a last parameter of Index.get.

The second issue is how the http.get method is stubbed:

httpSpy = stub(http, 'get')

This line basically means: replace http.get with an empty function. That dummy function won't throw if you pass a callback inside but it won't call it.

That is why http.get is called only once. It simply ignores the passed callback where http.get should be called the second time.

To fix this we can use stub.yields() method to make sinon aware that the last parameter passed to a stub is a callback (and sinon needs to call it). You can find the method in the docs.

Here is a working example, please see my comments:

class Index {
    // Added a callback here
    get(uri, callback) {
        http.get(uri, () => {
            http.get('/', () => {
                // Pass any data you want to return here
                callback(null, {});
            })
        })
    }
}

let httpSpy;

beforeEach(() => {
    a = new Index()
    // Now sinon will expect a callback as a last parameter and will call it
    httpSpy = stub(http, 'get').yields();
})

describe('When the get method is invoked', () => {
    const uri = 'http://www.google.co.uk';

    // Now we are waiting for the execution to end before any assertions
    beforeEach(done => {
        a.get(uri, done);
    });

    it('should make a call to the http service with passed in uri', () => {
        assert.calledTwice(httpSpy);
        assert.match(httpSpy.getCall(0).args[0], uri);
        assert.match(httpSpy.getCall(1).args[0], '/');
    });
})

Upvotes: 2

Related Questions