Javascript 테스팅 with Jest #2

PEPPERMINT100·2020년 11월 12일
1

Mock

이전 글에서 Jest의 기본적인 test 메소드를 통해 다양한 테스트를 해보았다. 이번엔 함수와 함수의 파라미터, 그리고 함수의 리턴값 등을 테스트할 수 있는 Mock에 대해서 알아보도록 하겠다. 일단 공식 문서의 예제를 보면

function forEach<T>(items: T[], callback: (items: T) => T) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

이렇게 forEach 함수를 구현해 놓고 이 함수에 대한 테스트를 한다.


const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

test('testing foreach', () => {
    // The mock function is called twice
    expect(mockCallback.mock.calls.length).toBe(2);

    // The first argument of the first call to the function was 0
    expect(mockCallback.mock.calls[0][0]).toBe(0);

    // The first argument of the second call to the function was 1
    expect(mockCallback.mock.calls[1][0]).toBe(1);

    // The return value of the first call to the function was 42
    expect(mockCallback.mock.results[0].value).toBe(42);

    expect(mockCallback.mockReturnValueOnce([42, 43]));

    console.log(mockCallback.mock.calls);
    console.log(mockCallback.mock.instances);
})

먼저 jest.fn을 통해 mock 함수를 만드는데 그 안에 x => 42 +x로 각 값에 42를 더한 값을 테스트하도록 한다.

그리고 forEach([0,1], mockCallback);을 통해 [0,1]에 대해 mock 함수를 테스트한다. const mockCallback = jest.fn(x => 42 + x);을 통해 파라미터와 리턴 값을 테스트 해줄 수 있다.

그리고 mock function의 mock 프로퍼티를 이용하여 데이터의 인스턴스와 각 테스트에 적용된 파라미터들을 볼 수도 있다.

그리고 mock function의 리턴 값을 임의로 지정해줄 수도 있는데

const filterTestFn = jest.fn();

// Make the mock return `true` for the first call,
// and `false` for the second call
test('set mockfunction\'s return value', () => {
    filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

    const result = [11, 12].filter(num => filterTestFn(num));

    console.log(result);
    // > [11]
    console.log(filterTestFn.mock.calls);
    // > [ [11], [12] ]
})

위와 같이 mockReturnValueOnce를 하나는 true 또 하나는 false로 지정하여 filter의 처음거는 통과하고 두 번째거는 통과하지 않도록 할 수도 있다. 그리고 mock.calls로 어떤 파라미터로 mock function을 call 했는지도 확인을 한 코드이다. 위 코드들은 전부 공식 문서에 있는 예제들이다.

const sayHello = (name:string) => {
  return `hello ${name}`;
}

test('mock sayHello', async () => {
  const mock = jest.fn(sayHello);
  expect(mock('tom')).toBe('hello tom');
  expect(mock).toHaveBeenCalledWith('tom');
})

위와 같이 이름을 넣으면 hello가 붙은 문자열을 리턴하는 함수를 만들고 아래와 같이 mockFunction화 한다음 toBe를 통한 리턴값, 그리고 toHaveBeenCalledWith을 통해 파라미터 값도 테스트를 할 수 있다.

Mock Implementation

Mock Implementation을 이용하면 Mock함수의 리턴 값을 아예 새로 지정할 수 있다.

const sayHello = (name:string) => {
  return `hello ${name}`;
}

test('mock sayHello', async () => {
  const mock = jest.fn(sayHello);
  expect(mock('tom')).toBe('hello tom');
  expect(mock).toHaveBeenCalledWith('tom');

  mock.mockImplementation(() => 'I lost my name');
  expect(mock('kelly')).toBe('I lost my name');
})

mock 함수 내에 들어간 kelly는 크게 상관이 없고 mock의 리턴 값을 implementation을 통해 바꿔 주었기 때문에 toBe에 다른 값을 넣어야 테스트가 통과되는 것을 확인할 수 있다.

  mock.mockImplementationOnce(() => 'I lost my name');
  expect(mock('kelly')).toBe('I lost my name');
  expect(mock('kelly')).toBe('hello kelly');

위와 같이 mockImplementationOnce를 사용하면 한 번만 implementation이 되기 때문에 두 번째 expect부터는 원래의 sayHello의 리턴값이 돌아오게 된다.

Testing Module

이번엔 패키지 매니저를 통해 다운받은 모듈의 사용을 테스트해보도록 하겠다. 많이 사용하는 axios 모듈을 테스트 해볼텐데 먼저

yarn add axios

를 통해 먼저 모듈을 다운 받는다. 그리고 kanye.ts, kanye.test.ts라는 파일을 만들고 아래와 같이 먼저 작성해보자.

import axios from "axios";

export const getKanyeTweet = async () => {
    const res = await axios.get("https://api.kanye.rest");
    console.log('getting data from real api');
    return res.data;    
}
// kanye.ts
import { getKanyeTweet } from "./kanye";

test("kanye test", async () => {
    await getKanyeTweet().then(res => {
        expect(res).toHaveProperty("quote");
    })
})
//kanye.test.ts

먼저 https://api.kanye.rest/는 힙합 아티스트 kanye west가 트위터에 올린 글귀를 랜덤하게 가져온다. 평소에 간단하게 api 테스트할때 자주 사용한다. get 방식으로 가져오면 보통

{
"quote": "If I don't scream, if I don't say something then no one's going to say anything."
}

이런 식으로 간단한 json 데이터를 생성해준다. 이 데이터를 가져오는 getKanyeTweet 함수를 만들고 테스트 파일에 가져와서 quote라는 프로퍼티를 가져오는지 체크한다. 테스트를 돌리면 정상적으로 통과하는 것을 확인할 수 있다. 하지만 일반적인 테스팅에서 보면 이 테스트 방식에는 약간의 문제가 있는데, 일단 우리는 api가 정상적으로 응답하는지 테스트하는 것이 아닌 테스트의 결과가 qoute 프로퍼티를 가지고 있는지 보고 싶은 것 이다. 그리고 이 테스트 방식은 매번 api call을 해야하기 때문에 서버에 계속해서 요청하고 응답을 받아야만 한다. 따라서 여기서도 우리는 mock function을 이용할 수 있다. mock function을 통해 가짜 api 함수를 만들고 그 결과로 테스팅을 하면 된다.

Mock API

먼저 node_modules와 같은 폴더에 __mocks__라는 폴더를 만들고 안에 kanye.ts라는 파일을 만든다. 여기서 폴더이름이 꼭 __mocks__가 되도록 해야 한다. 그리고 폴더 안 kanye.ts를 아래와 같이 작성한다.

export const getKanyeTweet = () => {
        return new Promise((resolve, reject) => {
            resolve({ quote : "Name one genius that ain't crazy"})
        }
    );
}
// __mocks__/kanye.ts

간단하게 가짜 데이터를 만들어주도록 한다. 그리고 테스트 파일에

jest.mock("./kanye");

import { getKanyeTweet } from "./kanye";

test("kanye test", async () => {
    await getKanyeTweet().then(res => {
        expect(res).toHaveProperty("quote");
    })
})

위와 같이 jest.mock("./kanye")라는 코드를 추가해준다. 이렇게하면 jest가 자동으로 __mocks__ 폴더를 찾아서 kanye 파일내의 코드를 mock function으로 만들어준다. 공식 문서를 보면 다음과 같이 적혀있다.

Doing so will lead Jest to automatically mock all your axios imports in your test files with the definitions laid out in the mock file.

그리고 테스트를 실행하면 정상적으로 통과한다. 여기서 jest.mock("./kanye") 부분을 주석처리 하고 실행하면

export const getKanyeTweet = async () => {
    const res = await axios.get("https://api.kanye.rest");
    console.log('getting data from real api');
    return res.data;    
}

실제 API 콜 함수의 console.log 내용이 출력되는데 다시 주석처리를 해제하면 jest가 mock function을 실행하기 때문에 테스트 결과에서 'getting data from real api'이 부분을 확인할 수 없을 것이다.

결론

이번엔 간단하게 jest의 mock에 대해서 알아보았다. 공식 문서와 구글을 봐도 테스트 자체의 목적이 뚜렷해서 공부하고 있는게 아닌지라 정확히 뭘 하고 있는지 몰라서 이해하기가 어려운 부분들이 많았다. 여기까지 jest의 기초를 조금 알아보았으니 다음 글부터는 React 코드를 테스팅해보도록 하겠다. 지금까지의 코드와 이후의 코드는 여기에서 확인할 수 있다.

profile
기억하기 위해 혹은 잊어버리기 위해 글을 씁니다.

0개의 댓글