-
Jest - Mock Function1 (모듈 mocking jest.fn( ), jest.mock( ))FrameWork/Jest 2023. 3. 9. 16:08
직접 작성한 모듈 모킹
실제 구현 코드1
messageService.js
module.export = function sendEmail(email, message) { /* 이메일 보낸는 코드 */ }; module.export = function sendSMS(phone, message) { /* 문자를 보내는 코드 */ };
userService.js
const { sendEmail, sendSMS } = require("./messageService"); exports.register = function register(user) { /* DB에 회원 추가 */ const message = "회원 가입을 환영합니다"; sendEmail(user.email, message); sendSMS(user.phone, message); }; exports.deregister = function deregister(user) { /* DB에 회원 삭제 */ const message = "탈퇴 처리 되었습니다."; sendEmail(user.email, message); sendSMS(user.phone, message); };
userService에서는 messageService에서 구현한 send매서드 들을 불서와서 사용한다.
jest.fn( )을 이용한 모듈 모킹(실행 실패❌)userService가 회원 가입 혹은 탈퇴 처리 시, 이메일과 문자를 보내기 위해서 messageService 모듈의 함수를 호출하는지 검증하는 테스트 코드를 작성한다.
여기서 messageService 모듈의 sendEmail 함수와 sendSMS 함수를 mock함수로 대체해야 한다. 왜냐하면 실제로 이메일이나 문자를 보낼 의도가 아니고 userService가 제대로 호출을 하는지 확인하는 것이 목적이기 때문이다.
이 상황에서 테스트를 작성할 때 흔히 하는 실수가 import한 함수를 저장하고 있는 변수에 mock함수를 바로 할당하려고 하는 것이다.
const { sendEmail, sendSMS } = require("../../service/messageService"); sendEmail = jest.fn(); // "sendEmail" is read-only sendSMS = jest.fn(); // "sendSMS" is read-only
위 방법은 자바스크립트 문법을 위반하기 때문에 컴파일 에러가 발생한다. (⚠️ import 방식으로 불러온 함수들은 기본이 const 변수이다.) 외부 모듈에서 불러온 함수는 보통const 변수로 이기 때문에 한번 초기화되면 다른 값으로 변경이 불가능하다.
조금은 억지 스럽지만 차선책으로 messageService모듈의 모든 함수를 하나의 객체로 불러오면, 객체의 속성으로 mock함수를 할당할 수 있다.
user1.test.js
const { register, deregister } = require("../../service/userService"); const messageService = require("../../service/messageService"); messageService.sendEmail = jest.fn(); messageService.sendSMS = jest.fn(); const sendEmail = messageService.sendEmail; const sendSMS = messageService.sendSMS; beforeEach(() => { sendEmail.mockClear(); sendSMS.mockClear(); }); const user = { email: "test@email.com", phone: "012-345-6789", }; test("register send messeges", () => { register(user); expect(sendEmail).toBeCalledTimes(1); expect(sendEmail).toBeCalledWith(user.email, "회원 가입을 환영합니다."); expect(sendSMS).toBeCalledTimes(1); expect(sendSMS).toBeCalledWith(user.phone, "회원 가입을 환영합니다."); }); test("deregister send messeges", () => { register(user); expect(sendEmail).toBeCalledTimes(1); expect(sendEmail).toBeCalledWith(user.email, "탈퇴 처리 되었습니다."); expect(sendSMS).toBeCalledTimes(1); expect(sendSMS).toBeCalledWith(user.phone, "탈퇴 처리 되었습니다."); });
이렇게 jest.fn() 또는 jest.spyOn() 함수를 이용해서 모듈을 모킹 하려고 하면 불필요한게 처리가 까다로워지는 경우가 많다. 예를 들어, messageService 모듈에서 제공하는 함수가 엄청 많다면 어떨까? 일일이 jest.fn()으로 모든 함수를 모킹하는 작업이 매우 번거로울 것이다.
jest.fn()과 jest.spyOn()함수를 사용하면 함수하나 하나를 모킹하는 것은 그다지 어렵지 않다. 하지만 여러 모듈을 import해서 사용하고 있는 코드에 대한 테스트를 작성하다 보면 단순히 함수 하나를 모킹하기 보다는 모듈 전체를 모킹하는 편이 더 유용한 경우가 많다.
jest.mock( )을 이용한 모듈 모킹(실행 성공👌)
Jest는 까다로울 수 있는 모듈 모킹을 좀 더 편하게 할 수 있도록 jest.mock()이라는 강력한 함수를 제공한다. 이 함수는 자동으로 모듈을 모킹 해주기 대문에 위와 같이 직접 일일이 모킹을 해줄 필요가 없다.
jest.mock('모듈 경로')
예를 들어 위 섹션에서 작성했던 테스트 코드를 jest.mock()을 이용해서 작성하면 아래와 같다. jest.mock( )함수는 첫 번째 인자로 넘어온 모듈 내의 모든 함수를 자동으로 mock함수로 바꿔준다.
user2.test.js
const { register, deregister } = require("../../service/userService"); const { sendEmail, sendSMS } = require("../../service/messageService"); jest.mock("../../service/messageService"); beforeEach(() => { sendEmail.mockClear(); sendSMS.mockClear(); }); const user = { email: "test@email.com", phone: "012-345-6789", }; test("register send messeges", () => { register(user); expect(sendEmail).toBeCalledTimes(1); expect(sendEmail).toBeCalledWith(user.email, "회원 가입을 환영합니다"); expect(sendSMS).toBeCalledTimes(1); expect(sendSMS).toBeCalledWith(user.phone, "회원 가입을 환영합니다"); }); test("deregister send messeges", () => { deregister(user); expect(sendEmail).toBeCalledTimes(1); expect(sendEmail).toBeCalledWith(user.email, "탈퇴 처리 되었습니다."); expect(sendSMS).toBeCalledTimes(1); expect(sendSMS).toBeCalledWith(user.phone, "탈퇴 처리 되었습니다."); });
위 코드에서 jest.mock('../../service/messageService') 호출이 userService 모듈을 임포트 하기전에 일어나야 되는 것이 아니냐고 생각 할 수도 있을 것이다. 하지만 Jest는 jest.mock( )함수 호출을 테스트 파일의 맨 위로 자동으로 hoist시켜 주기 때문에 jest.mock()의 호출 위치는 크게 걱정하지 않아도된다.
외부모듈의 모킹하고 해당 모듈의 비동기 함수 활용
실제 구현 된 API 모듈2
lib/users.js
const axios = require("axios"); module.exports = class Users { static all() { return axios.get("/users.json").then((res) => res.data); } };
jest.mock으로 구현된 모듈 모킹
user3.test.js
const axios = require("axios"); const Users = require("../../lib/users"); jest.mock("axios"); describe("Mocking Mocules", () => { test("should fetch users", async () => { const users = [{ name: "Bob" }]; const res = { data: users }; axios.get.mockResolvedValue(res); //아래 코드와 동일하게 동작 //axios.get.mockImplementation(()=> Promise.resolve(res)) const result = await Users.all(); expect(result).toEqual(users); }); });
참조
'FrameWork > Jest' 카테고리의 다른 글
Jest - Jest Object1 (jest.spyOn( )) (0) 2023.03.10 Jest - Module Function2 (mock implementations, mock Name) (0) 2023.03.09 Jest - beforeEach (0) 2023.03.06 Jest - create( ) - node-mock-http (0) 2023.03.06 Jest - TDD 개발 방식 순서 (0) 2023.03.06