How to test failed request with axios / jest

Viewed 3493

I've created a very small app that calculates the total price to pay for certain crypto if you pass in the coin and quantity. I want to test for errors but I am always getting "Received promise resolved instead of rejected". I believe this is due to the fact that if the url is wrong axios still resolves the promise.

The second problem I have is that I have tried testing if the url is correct but I have an issue that headers are part of the url and I can't see to figure out how to test just the url path without headers.

Those are tests 2 and 3 (Test number 1 is working)

// getTotalPrice.js
const axios = require("axios");

let symbol = process.argv[2];
let quantity = process.argv[3];

const API = `https://rest.coinapi.io/v1/exchangerate`;
const headers = {headers:{"X-CoinAPI-Key": "MY TOKEN"},};

const getTotalPrice = async (symbol = "BTC", quantity = 1) => {
  try {
  let res = await axios.get(`${API}/${symbol}/USD`, headers);
  let data = res.data;
  return Math.round(data.rate * quantity * 100) / 100;
  } catch(err) {
    console.log(err)
  }
};

getTotalPrice(symbol, quantity);

module.exports = {
  getTotalPrice,
  API
};

// getTotalPrice.test.js

const { get, getError } = require("axios");
const { getTotalPrice } = require("../getTotalPrice");
describe("getTotalPrice", () => {
  afterEach(() => {
    get.mockClear();
  });
  it("fetches data successfully from an api", async () => {
    const res = {
      data: {
        rate: 34000,
      },
    };
    get.mockImplementation(() => Promise.resolve(res));
    await expect(getTotalPrice()).resolves.toEqual(res.data.rate);
  });
  it("throws an error when incorrect data is passed", async () => {
    const errorMessage = "Wrong inputs passed in";

    getError.mockImplementationOnce(() => Promise.reject({}));
    await expect(getTotalPrice()).rejects.toThrowError();
  });
  it("uses correct url", async () => {
    const data = {
      data: {
        rate: 2000,
      },
    };
    get.mockImplementationOnce(() => Promise.resolve(data));

    await getTotalPrice("ETH");
    expect(get).toHaveBeenCalledWith(
      `${API}/ETH/USD`
    );
  });
});

// axios.js (in __mocks__)

module.exports = {
  get: jest.fn(() => Promise.resolve({ data: {} })),
  getError: jest.fn(() => Promise.reject()),
};

To recap, test 1 passes, test 2 fails with "Received promise resolved instead of rejected" and test 3 fails as I am getting headers as well not just url.

2 Answers

You should mock axios.get() with resolved/rejected value. Since you use the try...catch... statement, the error is thrown by axios.get() method will be caught, and you didn't rethrow any errors. So the assertion of test case 2 will not match toThrowError. Instead, you can use jest.spyOn() to add spy to console.log, and assert it to be called with the mocked Error.

Here is an solution only using jest.mock('axios') to mock axios module without __mocks__/axios.js file.

E.g.

getTotalPrice.js:

const axios = require('axios');

let symbol = process.argv[2];
let quantity = process.argv[3];

const API = `https://rest.coinapi.io/v1/exchangerate`;
const headers = { headers: { 'X-CoinAPI-Key': 'MY TOKEN' } };

const getTotalPrice = async (symbol = 'BTC', quantity = 1) => {
  try {
    let res = await axios.get(`${API}/${symbol}/USD`, headers);
    let data = res.data;
    return Math.round(data.rate * quantity * 100) / 100;
  } catch (err) {
    console.log(err);
  }
};

module.exports = { getTotalPrice, API };

getTotalPrice.test.js:

const axios = require('axios');
const { getTotalPrice, API } = require('./getTotalPrice');

jest.mock('axios');

describe('getTotalPrice', () => {
  it('fetches data successfully from an api', async () => {
    const res = { data: { rate: 34000 } };
    axios.get.mockResolvedValueOnce(res);
    await expect(getTotalPrice()).resolves.toEqual(res.data.rate);
  });
  it('throws an error when incorrect data is passed', async () => {
    const logSpy = jest.spyOn(console, 'log');
    const err = new Error('Wrong inputs passed in');
    axios.get.mockRejectedValueOnce(err);
    await getTotalPrice();
    expect(logSpy).toBeCalledWith(err);
  });
  it('uses correct url', async () => {
    const res = { data: { rate: 2000 } };
    axios.get.mockResolvedValueOnce(res);
    await getTotalPrice('ETH');
    expect(axios.get).toHaveBeenCalledWith(`${API}/ETH/USD`, { headers: { 'X-CoinAPI-Key': 'MY TOKEN' } });
  });
});

test result:

 PASS  examples/68200193/getTotalPrice.test.js (7.527 s)
  getTotalPrice
    ✓ fetches data successfully from an api (3 ms)
    ✓ throws an error when incorrect data is passed (14 ms)
    ✓ uses correct url (1 ms)

  console.log
    Error: Wrong inputs passed in
        at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/68200193/getTotalPrice.test.js:14:17
        at Generator.next (<anonymous>)
        at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/68200193/getTotalPrice.test.js:8:71
        at new Promise (<anonymous>)
        at Object.<anonymous>.__awaiter (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/68200193/getTotalPrice.test.js:4:12)
        at Object.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/68200193/getTotalPrice.test.js:12:66)
        at Object.asyncJestTest (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
        at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:45:12
        at new Promise (<anonymous>)
        at mapper (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
        at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:75:41

      at console.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
          at Generator.throw (<anonymous>)

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

A colleague recommended this to me, today, for mocking axios. I haven't tried it out, yet, though.

API mocking of the next generation Mock by intercepting requests on the network level. Seamlessly reuse the same mock definition for testing, development, and debugging.

https://mswjs.io/

Related