Jest Unit test + received undefined

Tags: , , ,



I am using Jest as my unit test framework. I am trying to mock third part npm “request” and executed my test cases, but i am receiving and the test fails

expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: 200

    Number of calls: 0

The following is my code:

spec.js

jest.mock('request', () => ({
  post: jest.fn()
}));
const request = require('request');

const mockRequest = (reqData) => {
    return {
      params: reqData.params? reqData.params:{} ,
      body: reqData.body?reqData.body:{},
      headers: reqData.headers?reqData.headers:{}
    };
  };

  const mockResponse = () => {
    const res = {};
    res.status = jest.fn().mockReturnValue(res);
    res.json = jest.fn().mockReturnValue(res);
    res.send = jest.fn().mockReturnValue(res);
    return res;
  };
  describe("Test suite for controller", () => {   
    test("should return true for successful validation",async () => {
request.post.mockResolvedValue({
        "key1":"value1",
        "key2":"value2"
      });
 const req = mockRequest();
      const res = mockResponse();
      const Ctrl = require('../../controllers/ctrl')
      await Ctrl.validate(req, res);
      //const result = await res1.json();
      expect(res.status).toHaveBeenCalledWith(200); 
});
});

Ctrl.js

const request = require('request');
module.exports = {
  async validate(req, res) {
      var postBody = {
        url: url,
        form: body,
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      };      

      await request.post(postBody, function (error, response, body) { 
        if (error) {
           return res.status(200).json({ result: "false" });
        } else {
            return res.status(200).json({ result: "true" });
        }
     });
});
}

Please share your ideas. Thanks in adavnce.

Answer

  1. You don’t need to use await with the callback of request.post, just choose one of them. Don’t use them together. Choose Promise or error-first Callback for handling the asynchronous code.
  2. You should mock the implementation of request.post so that you can get the callback function in your test case. Then, you can pass the successful response or error to this callback and test the code logic of callback.
  3. You can use mockFn.mockReturnThis() for mocking the chain methods of res object.

Here is the unit test solution:

ctrl.js:

const request = require('request');

module.exports = {
  async validate(req, res) {
    var postBody = {
      url: 'url',
      form: req.body,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    };

    request.post(postBody, function(error, response, body) {
      if (error) {
        return res.status(200).json({ result: 'false' });
      } else {
        return res.status(200).json({ result: 'true' });
      }
    });
  },
};

ctrl.spec.js:

const request = require('request');
const Ctrl = require('./ctrl');

jest.mock('request', () => ({
  post: jest.fn(),
}));

describe('Test suite for controller', () => {
  afterAll(() => {
    jest.resetAllMocks();
  });
  test('should return true for successful validation', async () => {
    request.post.mockImplementationOnce((body, callback) => {
      callback(null);
    });
    const req = { body: {} };
    const res = { status: jest.fn().mockReturnThis(), json: jest.fn() };
    await Ctrl.validate(req, res);
    expect(request.post).toBeCalledWith(
      {
        url: 'url',
        form: {},
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
      },
      expect.any(Function),
    );
    expect(res.status).toHaveBeenCalledWith(200);
    expect(res.json).toBeCalledWith({ result: 'true' });
  });

  test('should handle error for failed validation', async () => {
    const mErr = new Error('network');
    request.post.mockImplementationOnce((body, callback) => {
      callback(mErr);
    });
    const req = { body: {} };
    const res = { status: jest.fn().mockReturnThis(), json: jest.fn() };
    await Ctrl.validate(req, res);
    expect(request.post).toBeCalledWith(
      {
        url: 'url',
        form: {},
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
      },
      expect.any(Function),
    );
    expect(res.status).toHaveBeenCalledWith(200);
    expect(res.json).toBeCalledWith({ result: 'false' });
  });
});

unit test result with coverage report:

 PASS  src/stackoverflow/64311760/ctrl.spec.js (16.499s)
  Test suite for controller
    ✓ should return true for successful validation (16ms)
    ✓ should handle error for failed validation (2ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 ctrl.js  |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        19.59s


Source: stackoverflow