3 분 소요

들어가기 전에…
mock function이란 테스트 하기 위해 흉내만 내는 가짜 함수이다.

mock function이 필요한 이유?

user db에 접근해서 user list를 select 해오는 작업이 필요하다고 가정했을 때,

  1. 작성해야할 코드가 상당히 많아짐
  2. 외부 요인(네트워크 환경, db 상태 등)의 영향을 받음

테스트에서 동일한 코드는 동일한 결과를 내는 것이 중요하다.

mock function의 사용

jest.fn()으로 mock 함수를 만들 수 있다.

mock.calls

mock 함수 안에 있는 mock이라는 프로퍼티 안에는 calls라는 배열이 있다.
mock이라는 프로퍼티에는 두가지 정보가 저장되어 있다.

  1. 해당 함수가 몇 번 호출되었는지 (mock.calls.length)
  2. 호출될 때 어떤 인자가 전달되었는지 (mock.calls[][])

테스트 코드 - 1

fn.test.js

const mockFn = jest.fn();

mockFn();
mockFn(1);

test('의미없는 테스트', ()=>{
  console.log(mockFn.mock.calls); // [ [], [1] ] 출력
  expect('dd').toBe('dd');
});

test('함수는 2번 호출됨니다.', ()=>{
  expect(mockFn.mock.calls.length).toBe(2);
}); // 테스트 성공

test('2번째로 호출된 함수에 전달된 첫번째 인수는 1 입니다.', ()=>{
  expect(mockFn.mock.calls[1][0]).toBe(1);
}); // 테스트 성공

테스트 코드 - 2

fn.test.js

const mockFn=jest.fn();

function forEachAdd1(arr){ // 숫자가 들어있는 배열을 반복하며 1 증가한 값을 콜백함수에 전달
  arr.forEach(num => {
    // 콜백함수를 작성하지 말고 빠르고 간단하게 테스트하기 위해 목함수 활용
    mockFn(num+1)
  })
}

forEachAdd1([10,20,30])

test("함수 호출은 3번 됩니다.", ()=>{
  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)
})

mock.results

테스트 코드

인자와 리턴값이 있는 mock 함수
fn.test.js

const mockFn=jest.fn(num => num+1); // 1을 더한값 리턴

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

test("10에서 1 증가한 값이 반환됩니다.", ()=>{
  console.log(mockFn.mock.results); // 리턴값 확인
  expect(mockFn.mock.results[0].value).toBe(11)
});
test("20에서 1 증가한 값이 반환됩니다.", ()=>{
  expect(mockFn.mock.results[1].value).toBe(21)
});
test("30에서 1 증가한 값이 반환됩니다.", ()=>{
  expect(mockFn.mock.results[2].value).toBe(31)
});

리턴값

[
  { type: 'return', value: 11 },
  { type: 'return', value: 21 },
  { type: 'return', value: 31 }
]

mockReturnValueOnce

실행할 때마다 각각 다른 값을 요청할 수 있다.

테스트 코드 - 1

const mockFn = jest.fn();

mockFn
  .mockReturnValueOnce(10) // 중간에 리턴값을 바꾸려면 Once를 붙이기
  .mockReturnValueOnce(20) // 중간에 리턴값을 바꾸려면 Once를 붙이기
  .mockReturnValueOnce(30) // 중간에 리턴값을 바꾸려면 Once를 붙이기
  .mockReturnValue(40) // 마지막에는 Once를 빼기

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

test('의미없는 테스트', ()=>{
  console.log(mockFn.mock.results); // 리턴값 확인
  expect('dd').toBe('dd');
});

리턴값

[
  { type: 'return', value: 10 },
  { type: 'return', value: 20 },
  { type: 'return', value: 30 },
  { type: 'return', value: 40 }
]

테스트 코드 - 2

[1,2,3,4,5].filter(num => callback(num)); // 1부터 5까지 받아서 홀수만 리턴하는 함수

원래는 callback 함수가 짝수인지 홀수인지 판단하는 역할을 해주어야 한다.
하지만 아래와 같이, callback 함수 대신 mock 함수를 사용할 수 있다.

const mockFn = jest.fn();

mockFn // true와 false를 번갈아가며 리턴하는 mock 함수
  .mockReturnValueOnce(true) 
  .mockReturnValueOnce(false) 
  .mockReturnValueOnce(true) 
  .mockReturnValueOnce(false) 
  .mockReturnValue(true) 

const result = [1,2,3,4,5].filter(num => mockFn(num)); // 1부터 5까지 받아서 홀수만 리턴하는 함수

test("홀수는 1,3,5", ()=>{
  expect(result).toStrictEqual([1,3,5]); // 배열의 값을 비교하려면 toStrictEqual 사용
}); // 테스트 성공

mockResolvedValue

mockResolvedValue를 이용해 비동기 함수를 흉내낼 수 있다.

테스트 코드

const mockFn = jest.fn();

mockFn 
  .mockResolvedValue({name:"Mike"}); // name에 Mike 리턴

test("받아온 이름은 Mike", ()=>{
  mockFn().then(res => {
    expect(res.name).toBe("Mike");
  })
}); // 테스트 성공

mocking module의 사용

유저를 생성하는 코드를 테스트하고 싶은데 테스트할 때마다 유저가 새로 생긴다면 곤란한 상황
테스트가 끝날 때마다 롤백(rollback)하기도 번거로움

함수 작성

fn.js

const fn = {
  createUser: name => {
    console.log('실제로 사용자가 생성되었습니다.'); // 실제로는 유저 db에 유저를 생성해주는 함수라고 가정
    return {
      name,
    };
  },

};
module.exports=fn;

테스트 코드

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

// mocking module 사용
jest.mock("./fn"); // jest.mock을 통해 fn을 mocking model로 만든다.
fn.createUser.mockReturnValue({name:"Mike"}); // 실제 fn.createUser 함수는 호출되지 않고 해당 객체를 반환해주는 mock 함수가 존재할 뿐

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

위 코드에서 jest.mock("./fn");fn.createUser.mockReturnValue({name:"Mike"});를 주석처리한다면,
“실제로 사용자가 생성되었습니다.”라는 출력과 함께 db에 사용자가 생성된다.

🌟 유용한 Matchers: toBeCalled, toBeCalledTimes, toBeCalledWith, lastCalledWith

const mockFn = jest.fn();

mockFn(10,20);
mockFn();
mcokFn(30,40);

test("한번 이상 호출?", ()=>{
  expect(mockFn).toBeCalled();
}); // 테스트 통과
test("정확히 세번 이상 호출?", ()=>{
  expect(mockFn).toBeCalledTimes(3);
}); // 테스트 통과
test("10이랑 20을 전달받은 함수가 있는가?", ()=>{
  expect(mockFn).toBeCalledWith(10,20);
}); // 테스트 통과
test("마지막 함수는 30이랑 40을 받았나?", ()=>{
  expect(mockFn).lastCalledWith(30,40);
}); // 테스트 통과
  • toBeCalled: 한번이라도 호출되었으면 통과
  • toBeCalledTimes: 몇 번 호출되었는지 정확히 일치하면 통과
  • toBeCalledWith: 인수가 일치하는 함수가 있다면 통과
  • lastCalledWith: 마지막으로 실행된 함수의 인수가 일치하면 통과

참고

코딩앙마



💛 개인 공부 기록용 블로그입니다. 👻

맨 위로 이동하기