jest의 공식문서를 봐도 잘 이해가 가지 않아 찾아보던 중
이 분이 Mock에 대해 정리해 주셨는데 너무 쉽게 설명해주셔서 글을 가져와 보았다.
Mocking은 테스트를 독립시키기 위해 의존성을 개발자가 컨트롤하고 검사 할 수 있는 오브젝트로 반환하는 테크닉이다.
The Mock Function
Mockng의 목적은 우리가 컨트롤할 수 없는 무엇인가를 대체하는 것이기 때문에, 우리가 대체하는 것이 필요로하는 모든 기능을 갖고 있는게 중요하다
Mock 함수는 다음 기능을 제공한다.
- 함수 호출 Capture
- Return Value 설정
- 구현 변경하기
Mock 함수 인스턴스를 만드는 가장 간단한 방법은 jest.fn()와 jset Expect를 사용하면 캡처된 호출을 쉽게 테스트 할 수 있다.
test("returns undefined by default", () => {
const mock = jest.fn();
let result = mock("foo");
expect(result).toBeUndefined();
expect(mock).toHaveBeenCalled();
expect(mock).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledWith("foo");
});
그리고 Return Value, implementation, Promise Resolution을 바꿀 수도 있습니다.
test("mock implementation", () => {
const mock = jest.fn(() => "bar");
expect(mock("foo")).toBe("bar");
expect(mock).toHaveBeenCalledWith("foo");
});
test("also mock implementation", () => {
const mock = jest.fn().mockImplementation(() => "bar");
expect(mock("foo")).toBe("bar");
expect(mock).toHaveBeenCalledWith("foo");
});
test("mock implementation one time", () => {
const mock = jest.fn().mockImplementationOnce(() => "bar");
expect(mock("foo")).toBe("bar");
expect(mock).toHaveBeenCalledWith("foo");
expect(mock("baz")).toBe(undefined);
expect(mock).toHaveBeenCalledWith("baz");
});
test("mock return value", () => {
const mock = jest.fn();
mock.mockReturnValue("bar");
expect(mock("foo")).toBe("bar");
expect(mock).toHaveBeenCalledWith("foo");
});
test("mock promise resolution", () => {
const mock = jest.fn();
mock.mockResolvedValue("bar");
expect(mock("foo")).resolves.toBe("bar");
expect(mock).toHaveBeenCalledWith("foo");
});
이런식으로 mock를 통해 다양하게 조작할 수 있다.
이제 Mock 함수를 어떻게 사용할지 알아보자
의존성 주입
Mock 함수를 사용하는 일반적인 방법 중 하나는 테스트하려는 함수에 arguments를 직접 전달하는 것이다.
이를통해 테스트 대상을 실행한 Mock 함수가 어떤 arguments와 어떻게 실행됐는지 assert구문으로 확인해볼 수 있습니다.
const doAdd = (a, b, callback) => {
callback(a + b);
};
test("calls callback with arguments added", () => {
const mockCallback = jest.fn();
doAdd(1, 2, mockCallback);
expect(mockCallback).toHaveBeenCalledWith(3);
});
이 방법은 견고한 테스트를 만들지만 테스트코드가 의존성주입을 허용하도록 요구한다.
하지만 이렇게 할 수 없는 경우에는 실제로 존재하는 모듈과 함수를 Mocking 해야한다.
모듈과 함수를 Mocking하기
jest에서 모듈과 함수를 Mocing 하는 방법이 3가지 있다.
- jest.fn : Mock a function
- jest.mock : Mock a module
- jest.spyOn: Spy or mock a function
이 3가지 함수들은 각각의 방식으로 Mock 함수를 만드는데, 어떻게 동작하는지 설명하기 위해 다음과 같은 폴더 구조로 만들었다.
├ example/
| └── app.js
| └── app.test.js
| └── math.js
이 설정에서는 math.js 함수를 실제로 호출하지 않고 app.js를 테스트하면서, 함수가 예상대로 호출되는지 확인하기 위해 spy를 하는 것이 일반적이다.
예시들은 진부하지만 math.js의 함수들이 복잡한 계산을 하거나 개발자가 피하고싶은 IO를 만드는 요청이라고 생각하는 것을 추천한다.
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => b - a;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => b / a;
// app.js
import * as math from './math.js';
export const doAdd = (a, b) => math.add(a, b);
export const doSubtract = (a, b) => math.subtract(a, b);
export const doMultiply = (a, b) => math.multiply(a, b);
export const doDivide = (a, b) => math.divide(a, b);
jest.fn으로 Mocking 하기
가장 기본적인 방법은 함수를 Mock 함수로 재할당하는 것이다.
재할당된 함수가 쓰이는 어디서든지 Mock 함수가 원래의 함수 대신 호출될 것이다.
// mock_jest_fn.test.js
import * as app from "./app";
import * as math from "./math";
math.add = jest.fn();
math.subtract = jest.fn();
test("calls math.add", () => {
app.doAdd(1, 2);
expect(math.add).toHaveBeenCalledWith(1, 2);
});
test("calls math.subtract", () => {
app.doSubtract(1, 2);
expect(math.subtract).toHaveBeenCalledWith(1, 2);
});
이렇게 Mocking 하는 방식은 몇 가지 이유로 덜 사용되어진다.
- jest.mock는 자동적으로 모듈의 모든 함수를 Mocking 해준다.
- jest.spyOn도 마찬가지로 모든 함수를 Mocking해주면서 원래의 함수를 다시 복원할 수도 있다.
jest.mock으로 Mocking 하기
조금 더 일반적인 접근법은 자동적으로 모듈이 exports하는 모든 것들을 Mocking 해주는 jest.mock를 쓰는 것이다.
따라서 jest.mock('./math.js')를 해주면 본질적으로 math.js를 다음처럼 설정하는 것과 마찬가지가 된다.
(즉 jest.mock를 사용함으로써 jest.fn()을 일일이 적용해주는 번거로운 작업을 줄일 수 있다.)
// math.eg.js
export const add = jest.fn();
export const subtract = jest.fn();
export const multiply = jest.fn();
export const divide = jest.fn();
여기서부터 모듈이 exports하는 모든 것들에 Mock 함수 기능을 쓸 수 있다.
// mock_jest_mock.test.js
import * as app from "./app";
import * as math from "./math";
// Set all module functions to jest.fn
jest.mock("./math.js");
test("calls math.add", () => {
app.doAdd(1, 2);
expect(math.add).toHaveBeenCalledWith(1, 2);
});
test("calls math.subtract", () => {
app.doSubtract(1, 2);
expect(math.subtract).toHaveBeenCalledWith(1, 2);
});
이렇게 하는 것이 가장 쉽고 일반적인 Mockingg 방법이다.
이 방법의 유일한 단점은 모듈의 원래 구현에 접근하기 어렵다는 것이다.
이런 경우를 대비해 spyOn이 존재한다.
jest.spyOn으로 Spy 혹은 Mocking하기
때로는 우리는 메소드가 실행되는 것을 지켜보길 원할뿐만 아니라, 기존의 구현을 보존하길 바랍니다.
구현을 mocking하고 차후에 테스트구문에서 원본을 복원할 수 있습니다.
이 경우에 jest.spyOn을 쓸 수 있습니다.
단순히 math 함수에 Spy를 호출하고 원본 구현은 그대로 둘 수 있습니다.
import * as app from "./app";
import * as math from "./math";
test("calls math.add", () => {
const addMock = jest.spyOn(math, "add");
// calls the original implementation
expect(app.doAdd(1, 2)).toEqual(3);
// and the spy stores the calls to add
expect(addMock).toHaveBeenCalledWith(1, 2);
});
이것은 실제로 함수를 대체하지 않고, 특정한 사이드 이펙트가 발생하는지 테스트하는 몇몇 시나리오에 유용합니다.
함수를 Mocking하고 다시 원래 구현을 복원할 수도 있습니다.
import * as app from "./app";
import * as math from "./math";
test("calls math.add", () => {
const addMock = jest.spyOn(math, "add");
// override the implementation
addMock.mockImplementation(() => "mock");
expect(app.doAdd(1, 2)).toEqual("mock");
// restore the original implementation
addMock.mockRestore();
expect(app.doAdd(1, 2)).toEqual(3);
});
jest는 각각의 테스트 파일이 샌드박스화 되어 있기 때문에, afterAll 혹을 불필요하게 사용하지 않도록 하는 경우에 유용합니다.
import * as app from "./app";
import * as math from "./math";
test("calls math.add", () => {
// store the original implementation
const originalAdd = math.add;
// mock add with the original implementation
math.add = jest.fn(originalAdd);
// spy the calls to add
expect(app.doAdd(1, 2)).toEqual(3);
expect(math.add).toHaveBeenCalledWith(1, 2);
// override the implementation
math.add.mockImplementation(() => "mock");
expect(app.doAdd(1, 2)).toEqual("mock");
expect(math.add).toHaveBeenCalledWith(1, 2);
// restore the original implementation
math.add = originalAdd;
expect(app.doAdd(1, 2)).toEqual(3);
});
jest.spyOn을 이렇게 사용할 수 있습니다.
source : https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c
'정보' 카테고리의 다른 글
[Effective java] Item 1 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2024.02.18 |
---|---|
싱글톤 (0) | 2023.11.26 |
코드리뷰 잘 활용하기 (1) | 2023.10.23 |
코딩테스트할 때 유용한 코드들 (1) | 2023.10.01 |
Husky, lint-staged 설치 및 사용법 (0) | 2023.09.19 |