0
votes

I am pretty new to unit testing in node.js.

I have a function which looks something like this. I need to write a unit test for the function.

async refreshToken(req, res) {
    const token = req.body.token || req.query.token;
    const options = {method: 'POST', url: ''};
    let resp;
    try {
      resp = await request(options);
    } catch (e) {
      console.error(e);
    }
    if (resp) {
      const grant = {
        another_token: {
          token: resp.another_token,
        },
        expires_in: resp.expires_in
      }
      res.end(JSON.stringify(grant));
    } else {
      res.status(400).end("not authorized");
    }
  }

I am planning to use mocha framework with sinon and chai.

I tried a test using mocha, but could not figure out how to assert if the request(options) is called at least once.

describe('refreshToken', () => {
    it('should take token from body', async () => {
      const req = {
        body: {
          token: "123"
        }
      }
      await auth.refreshToken(req, res);
      request.should.have.been.calledOnceWithExactly(options);
    })

I get:

TypeError: Cannot read property 'xxxxx' of undefined

I am having difficulty how to make this work with mock/stub.

1
Please, show what have you tried in your test - Castro Roy
Updated with the test that I tried - sudo
Cannot read property 'xxxxx' of undefined but, what is 'xxxxx'? line of the error? You need to show more of your test. Where is options defined and initialized? Where do you spy on request? What is the order of your import or require? - Castro Roy

1 Answers

0
votes

Here is the unit test solution:

auth.ts:

import request from 'request-promise';

export async function refreshToken(req, res) {
  const token = req.body.token || req.query.token;
  const options = { method: 'POST', url: 'https://github.com/mrdulin' };
  let resp;
  try {
    resp = await request(options);
  } catch (e) {
    console.error(e);
  }
  if (resp) {
    const grant = {
      another_token: {
        token: resp.another_token
      },
      expires_in: resp.expires_in
    };
    res.end(JSON.stringify(grant));
  } else {
    res.status(400).end('not authorized');
  }
}

auth.spec.ts:

import sinon from 'sinon';
import proxyquire from 'proxyquire';
import { expect } from 'chai';

describe('auth', () => {
  describe('#refreshToken', () => {
    it('should take token from body', async () => {
      const mResponse = { another_token: '123', expires_in: '3600' };
      const requestPromiseStub = sinon.stub().resolves(mResponse);
      const auth = proxyquire('./auth', {
        'request-promise': requestPromiseStub
      });
      const req = {
        body: {
          token: '123'
        }
      };
      const res = { end: sinon.stub() };
      await auth.refreshToken(req, res);
      expect(requestPromiseStub.calledWith({ method: 'POST', url: 'https://github.com/mrdulin' })).to.be.true;
      expect(
        res.end.calledWith(
          JSON.stringify({
            another_token: {
              token: mResponse.another_token
            },
            expires_in: mResponse.expires_in
          })
        )
      ).to.be.true;
    });

    it('should cause not authorized error', async () => {
      const mError = new Error('network error');
      const requestPromiseStub = sinon.stub().rejects(mError);
      const auth = proxyquire('./auth', {
        'request-promise': requestPromiseStub
      });
      const errorLogSpy = sinon.spy(console, 'error');
      const req = {
        body: {
          token: '123'
        }
      };
      const res = { status: sinon.stub().returnsThis(), end: sinon.stub() };
      await auth.refreshToken(req, res);
      expect(errorLogSpy.calledWith(mError)).to.be.true;
      expect(res.status.calledWith(400)).to.be.true;
      expect(res.status().end.calledWith('not authorized')).to.be.true;
    });
  });
});

Unit test result with coverage report:

  auth
    #refreshToken
      ✓ should take token from body (1262ms)
Error: network error
    at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:26462
    at step (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:23604)
    at Object.next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:20644)
    at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:19788
    at new Promise (<anonymous>)
    at __awaiter (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:18995)
    at Context.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:26156)
    at callFn (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runnable.js:387:21)
    at Test.Runnable.run (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runnable.js:379:7)
    at Runner.runTest (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:535:10)
    at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:653:12
    at next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:447:14)
    at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:457:7
    at next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:362:14)
    at Immediate.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:425:5)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)
    at processImmediate (timers.js:658:5)
      ✓ should cause not authorized error (88ms)


  2 passing (1s)

--------------|----------|----------|----------|----------|-------------------|
File          |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files     |      100 |       75 |      100 |      100 |                   |
 auth.spec.ts |      100 |      100 |      100 |      100 |                   |
 auth.ts      |      100 |       75 |      100 |      100 |                 4 |
--------------|----------|----------|----------|----------|-------------------|

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