[JEST] Mock Function은 무엇일까?

mhlog·2023년 5월 21일
0

Jest

목록 보기
3/3
post-thumbnail

0. Jest에서 Mock이란?

Jest에서 "mock"은 테스트 중에 특정 객체나 함수의 동작을 흉내내거나 대체하는 기능을 의미한다. 이를 통해 테스트 시나리오에 필요한 가짜 동작을 정의하거나, 의존성을 가진 모듈을 격리하여 테스트할 수 있다. 즉, 테스트하기 위해서 흉내만 내는 함수라고 생각할 수 있겠다.

만약 UserDB에 접근해서 userList를 select해오는 작업이 필요하다고 가정해보자. 이를 직접 구현하려고 한다면 다음과 같은 문제점들이 발생한다.

  • 구현하기 위해 작성해야될 코드가 많아진다. 테스트를 위한 코드보다 길어질 수 있다.
  • 외부 요인에 영향을 받는다. (네트워크 환경, DB 상태 등)
  • “테스트는 deterministic 해야한다." 라는 원칙에 위배됨. 왜냐하면 단위 테스트가 단독으로 고립되어 있지 않고, 외부 환경에 의존하기 때문이다.

테스트에서는 같은 코드는 동일한 결과를 내는 것이 중요하기 때문에 mock function을 쓰는 것이 효율적일 것이다.

1. jest.fn() 사용법

// mock function
const mockFn = jest.fn();

function forEachAdd1(arr) {
  arr.forEach(num => {
    // fn(num+1)
    mockFn(num+1);
  })
}
forEachAdd1([10, 20, 30]);

test("함수 호출은 3번 됩니다.", () => {
  console.log(mockFn.mock.calls);
  expect(mockFn.mock.calls.length).toBe(3);
})
test("전달된 값은 11, 21, 31입니다.", () => {
  expect(mockFn.mock.calls[0][0]).toBe(11);
  expect(mockFn.mock.calls[1][0]).toBe(21);
  expect(mockFn.mock.calls[2][0]).toBe(31);
})

각 배열에서 forEach 문을 돌아서 배열의 모든 요소에 1을 추가하는 함수를 구현해본다고 하자. fn이란 함수를 따로 정의하여 1을 증가시킬 수도 있지만, mock Function을 이용하여 함수를 따로 정의하지 않고 구현한 모습이다. 위 사진에서 console로 확인할 수 있듯이 mockFn.mock.call에는 현재까지 호출되어 저장된 값이 배열로 저장된다.

다음은 mock.results에 대해서 알아보자.

// mock function
const mockFn = jest.fn(num => num + 1);

mockFn(10);
mockFn(20);
mockFn(30);

test("함수 호출은 3번 됩니다.", () => {
  console.log(mockFn.mock.results);
  expect(mockFn.mock.results.length).toBe(3);
})

처음 구현한 함수에서 방식을 배열을 받지 않고 값을 받는 형태로 변경한 모습이다. results에는 다음과 같은 값들이 들어간다.

const mockFn = jest.fn();
mockFn
  .mockReturnValueOnce(10)
  .mockReturnValueOnce(20)
  .mockReturnValueOnce(30)
  .mockReturnValue(40)

mockFn();
mockFn();
mockFn();
mockFn();

test("함수 호출은 4번 됩니다.", () => {
  console.log(mockFn.mock.results);
  expect(mockFn.mock.results.length).toBe(4);
})

mockReturnValue(리턴 값) 함수를 이용해서 가짜 함수가 어떤 값을 리턴해야할지 설정해줄 수 있다.

const mockFn = jest.fn();

mockFn
  .mockResolvedValue({ name: "kim"})

test("받아온 이름은 Kim", () => {
  mockFn().then(res => {
    expect(res.name).toBe("kim")
  })
})

또한, mockResolvedValue를 통해서 Promise처럼 동작하는 가짜 비동기 함수를 만들 수도 있다.

2. jest.spyOn() 사용법

mocking에는 spyOn이라는 함수를 사용할 수 있다. 어떤 객체에 속한 함수의 구현을 가짜로 대체하지 않고, 해당 함수의 호출 여부와 어떻게 호출되었는지만을 알아내야 할 때가 있다. 이럴 때, Jest에서 제공하는 jest.spyOn(object, methodName) 함수를 이용하면 된다.

// fn.js
const fn = {
  add: (a, b) => a + b,
};

// fn.test.js
const fn = require('./fn');
const spyFn = jest.spyOn(fn, "add");

const result = fn.add(2, 3);
expect(spyFn).toBeCalledTimes(1);
expect(spyFn).toBeCalledWith(2, 3);
expect(result).toBe(5);

jest.spyOn() 함수를 이용해서 fn 객체의 add라는 함수에 스파이를 붙였습니다. 따라서 add 함수를 호출 후에 호출 횟수와 어떤 인자가 넘어갔는지 감증할 수 있다. 하지만 가짜 함수로 대체한 것은 아니기 때문에 결과 값은 원래 구현대로 2와 3의 합인 5가 되는 것을 알 수 있다.

3. jest.mock() 사용법

만약 DB에 접속해서 user의 가입 정보를 저장하는 함수가 있다고 가정해보자. test를 하기 위해서 createUser 함수를 쓰는 경우, 실제로 함수를 호출하게 되면 DB에 다시 접속해서 저장된 user의 값을 지워주어야 한다. 이럴때 사용하면 좋은 것이 jest.mock()를 이용한 module mocking이다.

const fn = require('./fn');

jest.mock("./fn");
fn.createUser.mockReturnValue({ name: "Mike"});

test("유저를 만든다", () => {
  const user = fn.createUser("Mike");
  expect(user.name).toBe("Mike");
})

위와 같이 사용하면 실제로 user의 정보를 DB에 저장하지 않고 Test를 수행할 수 있다.

4. jest에서 사용하는 유용한 Matcher

  • toBeCalled(): 한번이라도 호출되었으면 통과됨.
  • toBeCalledTimes(): 정확히 몇번 호출되었는지를 확인하고 맞으면 통과됨.
  • toBeCalledWith(): 인수로 어떤 값들을 받았는지 체크하고 인수로 받은 값을 받은 적이 있다면 통과됨.
  • lastCalledWith(): 마지막으로 실행된 인수를 체크하고 맞으면 통과됨.

0개의 댓글