My Dream Being Visualized

Jest를 활용한 TDD 단위 테스트 - 7편 (feat. axios) 본문

Backend/TDD

Jest를 활용한 TDD 단위 테스트 - 7편 (feat. axios)

마틴킴 2021. 12. 21. 07:20
728x90

 개인 공부를 위한 공간입니다. 틀린 부분 지적해주시면 감사하겠습니다 (_ _)

 

지난 시간에, 기본적으로 Mock 함수를 만드는 방법에 대해서 알아보았다.

 

이번 시간엔, axios 모듈을 활용한 API 호출 시 Mock 함수를 생성하고 테스트 하는 방법에 대해서 알아볼 예정이다!

https://jestjs.io/docs/mock-functions

 

 

Mocking Modules

API 호출을 통해 유저 정보를 주는 클래스가 있으며, axios 모듈을 사용하여 API를 호출하고, 유저의 정보를 담고있는 data를 반환한다고 가정하자.

import axios from 'axios';

class Users {
  static all() {
    return axios.get('/users.json').then(resp => resp.data);
  }
}

export default Users;

느리고 민감한 테스트일 수 있는 실제 API 호출없이, axios 모듈을 자동으로 테스트를 하기 위해서 jest.mock(...)를 사용하자.

jest.fn()은 내가 원하는 함수를 만들어서 실행시킬 수 있는 메소드이며
jest.mock()은 특정 모듈에 대해 필요할 때 자동 mock 버전으로 모듈을 mock하는 메소드이다.

axios 모듈을 mock하면, get 메소드에 대해 mockResolvedValue를 제공하며 테스트에 사용될 데이터를 리턴할 수 있다.

그 결과, axios.get('/users.json')은 fake response를 리턴하게 만들었다.

import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);

  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))

  return Users.all().then(data => expect(data).toEqual(users));
});

 

여기까지가 docs에 나와있는 내용이다.
이해가 100% 되질 않아, 링크 하나를 번역 & 참고하였다.

https://vhudyma-blog.eu/3-ways-to-mock-axios-in-jest/

 

3 Ways To Mock Axios In Jest

Learn three ways to mock Axios in Jest and test API requests - jest.mock() function, jest-mock-axios, and axios-mock-adapter libraries.

vhudyma-blog.eu

 

테스트에서 실제 API를 호출하는 방식은 옳지 않기 때문에, mock 함수를 생성해야 한다.

(큰 공감을 하는 게, 크롤링 혹은 3rd API 호출 시 항상 test.js 라는 파일을 생성하여 1차이자 마지막으로 테스트하곤 했다...)

위 글의 필자는, Jest 라이브러리를 통해 테스트하는 방법이 여러가지 있지만, 가장 유용한 3가지 방법을 가지고 설명한다.

 

우선 우리가 테스트 할 환경을 갖춘 뒤, utils.js라는 파일을 생성한다.

const axios = require("axios");

const BASE_URL = "https://jsonplaceholder.typicode.com";

const fetchUsers = async () => {
  try {
    return await axios.get(`${BASE_URL}/users`);
  } catch (e) {
    return [];
  }
};

module.exports = {
  BASE_URL,
  fetchUsers
}

 

Way #1 - jest.mock()

axios를 jest를 통해 사용하는 가장 쉬우며 유명한 방법이라고 한다. utils.test.js 파일을 생성한다.

const axios = require("axios");

const { BASE_URL, fetchUsers } = require('./utils');

// 1) Axios Mock 함수를 만든다.
jest.mock("axios")

describe("fetchUsers", () => {
  describe("when API call is successful", () => {
    it("should return users list", async () => {
      // given
      const users = [
        { id: 1, name: "John" },
        { id: 2, name: "Andrew" },
      ];
      // 2) response할 샘플을 만들고, axios mock 함수가 리턴을 하게 만든다.
      //    post 메소드를 쓴다면, axios.post가 되어야 한다.
      //    (axios는 Promise를 return하기 때문에 resolve!)
      axios.get.mockResolvedValueOnce(users);

      // when
      // 3) 테스트 할 fetchUsers() 함수를 호출한다.
      const result = await fetchUsers();

      // then
      // 4) endpoint 명시를 해주고, 정확한 결과값을 리턴하게 해준다.
      expect(axios.get).toHaveBeenCalledWith(`${BASE_URL}/users`);
      expect(result).toEqual(users);
    })
  })
  describe("when API call fails", () => {
    it("should return empty users list", async () => {
      // given
      const message = "Network Error";
      axios.get.mockRejectedValueOnce(new Error(message));

      // when
      const result = await fetchUsers();

      // then
      expect(axios.get).toHaveBeenCalledWith(`${BASE_URL}/users`);
      expect(result).toEqual([]);
    })
  })
})
성공적으로 테스트가 완료됨을 확인할 수 있다!

 

Way #2 - jest-mock-axios

jest-mock-axios라는 헬퍼 라이브러리를 활용한다.

테스트를 해보다가, 사용하는데 실패해서 우선은 넘어가고자 한다.
궁금하신 분은 링크를 통해 확인하시길 바란다 ㅜㅜ

 

Way #3 - axios-mock-adapter

axios-mock-adapter라는 헬퍼 라이브러리를 활용한다.

npm install -D axios-mock-adapter
const axios = require("axios");
const MockAdapter = require("axios-mock-adapter");

const { BASE_URL, fetchUsers } = require("./utils");

describe("fetchUsers", () => {
  let mock;

  beforeAll(() => {
  	// Axios Mock Adapter를 생성한다.
    mock = new MockAdapter(axios);
  });

  afterEach(() => {
  	// afterEach라는 훅에 매 테스트 이후에 리셋을 해줄 mock.reset()을 사용한다.
    mock.reset();
  });

  describe("when API call is successful", () => {
    it("should return users list", async () => {
      // given
      const users = [
        { id: 1, name: "John" },
        { id: 2, name: "Andrew" },
      ];
      // Get 메소드 호출
      mock.onGet(`${BASE_URL}/users`).reply(200, users);

      // when
      const result = await fetchUsers();

      // then
      expect(mock.history.get[0].url).toEqual(`${BASE_URL}/users`);
      expect(result.data).toEqual(users);
    });
  });

  describe("when API call fails", () => {
    it("should return empty users list", async () => {
      // given
      mock.onGet(`${BASE_URL}/users`).networkErrorOnce();

      // when
      const result = await fetchUsers();

      // then
      expect(mock.history.get[0].url).toEqual(`${BASE_URL}/users`);
      expect(result).toEqual([]);
    });
  });
});

 

NPM Trends

외부 라이브러리를 활용한 Axios를 mock하는데엔 2가지 방법이 있었다.

NPM Trends(https://www.npmtrends.com/jest-mock-axios-vs-axios-mock-adapter)에 따르면, axios-mock-adapter가 훨씬 더 많은 다운로드가 이루어지고 있다는 것을 알 수 있다.

jest-mock-axios VS axios-mock-adapter

 

Which Way Is Best?

'필요에 따라 다르다.'

간단한 API를 테스트 한다면 외부 라이브러리를 활용할 필요 없이 Way #1 jest.mock()을 쓰면 된다.

외부 라이브러리는 테스트하는데에 어려운 케이스를 매우 쉽게 만들어주는 많은 유용한 기능을 제공한다.

우선 외부 라이브러리 없이 사용해볼 것을 권장하며, 어려운 상황에 마주했을 때 외부 라이브러리를 통해 해결할 수 있는지 보는 것이 좋다.

 

Summary

API 호출을 mock 함수화해서 테스트 하는 것은 좋다. 실제 API를 호출할 필요가 없을 뿐더러, 200, 400 혹은 네트워크 에러 메세지와 함께 fail을 리턴하는 특정 케이스들도 처리할 수 있기 때문이다.

 

여기까지가 위 링크를 보고 번역한 글이다.
(100% 번역한 글은 아니다!)

정리해주신 필자에게 감사하다!

 

이제 몇편 남지 않았다.
빠르게 훑고 정리한 뒤 코드 작성하러 가자!