Search code examples
javascriptunit-testingmockingmocha.jssinon

Unit test a function which has a function with callback (request module)


I am making testing in my NodeJS project and I have the following function I want to unit test:

function myRequest(targetUrl, reqBody) {
  return new Promise((resolve, reject) => {
    request.post(targetUrl, { json: reqBody }, (error, response, body) => {
      if (!error && response.statusCode === 200) {
        resolve(body.transferId);
      } else {
        reject(error || body.description || body);
      }
    });
  });
}

I am using mocha and sinon. How can I test this function?

Firstly, I tested using my own mocked request module for the success scenario. Now, I want to do the error scenario, which could be the post function gets an error. How could I do it without changing or making a new one mock of request (to return an error)? Is is possible?


Solution

  • Here is the unit test solution, you should use sinon.stub:

    E.g. index.ts:

    import request from 'request';
    
    export function myRequest(targetUrl, reqBody) {
      return new Promise((resolve, reject) => {
        request.post(targetUrl, { json: reqBody }, (error, response, body) => {
          if (!error && response.statusCode === 200) {
            resolve(body.transferId);
          } else {
            reject(error || body.description || body);
          }
        });
      });
    }
    

    index.spec.ts:

    import { myRequest } from '.';
    import chai from 'chai';
    import sinon from 'sinon';
    import chaiAsPromised from 'chai-as-promised';
    import request from 'request';
    chai.use(chaiAsPromised);
    
    const { expect } = chai;
    
    describe('myRequest', () => {
      it('should request success', async done => {
        // @ts-ignore
        const stub = sinon.stub(request, 'post').callsFake((uri, options, callback) => {
          const mResponse = { statusCode: 200 };
          const mBody = { transferId: 1 };
          callback(null, mResponse, mBody);
          done();
        });
        const actualValue = await myRequest('url', {});
        // @ts-ignore
        stub.calledOnceWith('url', { json: {} });
        expect(actualValue).to.eq(1);
        stub.restore();
      });
    
      it('should throw error use request error', async done => {
        const mError = new Error('Internal server error');
        const mResponse = { statusCode: 500 };
        // @ts-ignore
        const stub = sinon.stub(request, 'post').callsFake((uri, options, callback) => {
          callback(mError, mResponse, null);
          done();
        });
        await expect(myRequest('url', {})).to.be.rejectedWith(mError);
        // @ts-ignore
        stub.calledOnceWith('url', { json: {} });
        stub.restore();
      });
    
      it('should throw error use body.description as error message', async done => {
        const mResponse = { statusCode: 500 };
        const mBody = { description: 'some error' };
        // @ts-ignore
        const stub = sinon.stub(request, 'post').callsFake((uri, options, callback) => {
          callback(null, mResponse, mBody);
          done();
        });
        await expect(myRequest('url', {})).to.be.rejectedWith(mBody.description);
        // @ts-ignore
        stub.calledOnceWith('url', { json: {} });
        stub.restore();
      });
    
      it('should throw error use body as error message', async done => {
        const mResponse = { statusCode: 500 };
        const mBody = 'some error';
        // @ts-ignore
        const stub = sinon.stub(request, 'post').callsFake((uri, options, callback) => {
          callback(null, mResponse, mBody);
          done();
        });
        await expect(myRequest('url', {})).to.be.rejectedWith(mBody);
        // @ts-ignore
        stub.calledOnceWith('url', { json: {} });
        stub.restore();
      });
    });
    

    Unit test result with 100% coverage:

      myRequest
        ✓ should request success
        ✓ should throw error use request error
        ✓ should throw error use body.description as error message
        ✓ should throw error use body as error message
    
    
      4 passing (13ms)
    
    ---------------|----------|----------|----------|----------|-------------------|
    File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ---------------|----------|----------|----------|----------|-------------------|
    All files      |      100 |      100 |      100 |      100 |                   |
     index.spec.ts |      100 |      100 |      100 |      100 |                   |
     index.ts      |      100 |      100 |      100 |      100 |                   |
    ---------------|----------|----------|----------|----------|-------------------|
    

    Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/58822996