[Node.js] Jest - 4. 목(Mock) 함수
들어가기 전에…
mock function이란 테스트 하기 위해 흉내만 내는 가짜 함수이다.
mock function이 필요한 이유?
user db에 접근해서 user list를 select 해오는 작업이 필요하다고 가정했을 때,
- 작성해야할 코드가 상당히 많아짐
- 외부 요인(네트워크 환경, db 상태 등)의 영향을 받음
테스트에서 동일한 코드는 동일한 결과를 내는 것이 중요하다.
mock function의 사용
jest.fn()
으로 mock 함수를 만들 수 있다.
mock.calls
mock 함수 안에 있는 mock
이라는 프로퍼티 안에는 calls
라는 배열이 있다.
mock
이라는 프로퍼티에는 두가지 정보가 저장되어 있다.
- 해당 함수가 몇 번 호출되었는지 (
mock.calls.length
) - 호출될 때 어떤 인자가 전달되었는지 (
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
: 마지막으로 실행된 함수의 인수가 일치하면 통과
참고
💛 개인 공부 기록용 블로그입니다. 👻