> [!date] published: 2025-06-06
- [The Jest Object · Jest](https://jestjs.io/docs/jest-object)
- [Manual Mocks · Jest](https://jestjs.io/docs/manual-mocks)
# Jest Mock
Jest에서 mock을 다룰 때는 새 mock 함수를 만들 것인지, 모듈 전체를 대체할 것인지, 기존 메서드를 감시할 것인지를 먼저 구분한다.
## `jest.fn(implementation?)`
> 새로운 mock 함수 생성
선택적으로 내부 구현을 함께 전달할 수 있다. 기본적으로는 `undefined`를 반환함.
```ts
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
// With a mock implementation:
const returnsTrue = jest.fn(() => true);
console.log(returnsTrue()); // true;
```
## jest.mock(moduleName, factory, options)
> 전체 모듈을 mock으로 대체하는 것
모듈 전체를 자동으로 모킹한다.
import 시점에서부터 mock이 적용됨 <== 호이스팅되어 최상단에서 실행된다.
기본 사용
```ts
// banana.ts
module.exports = () => "banana";
```
```ts
jest.mock("../banana");
const banana = require("../banana"); // banana will be explicitly mocked.
banana(); // will return 'undefined' because the function is auto-mocked.
```
팩토리 인자와 함께 사용하기
```ts
jest.mock("../moduleName", () => {
return jest.fn(() => 42); // 👈
});
// This runs the function specified as second argument to `jest.mock`.
const moduleName = require("../moduleName");
moduleName(); // Will return '42';
```
ES6의 default export를 사용하는 모듈일 때: `__esModule: true` 프로퍼티가 필요하다.
```ts
import moduleName, { foo } from "../moduleName";
jest.mock("../moduleName", () => {
return {
__esModule: true,
default: jest.fn(() => 42),
foo: jest.fn(() => 43),
};
});
moduleName(); // Will return 42
foo(); // Will return 43
```
모듈의 일부분만 모킹해야 할 때
```ts
jest.mock("./mathUtils", () => ({
...jest.requireActual("./mathUtils"), // 실제 구현 유지
complexCalculation: jest.fn(() => "mocked result"), // 특정 함수만 Mock
}));
```
## jest.spyOn(object, methodName)
> 기존 메서드를 감시하면서 mock 기능을 추가하는 것
기본적으로는 원본 구현을 유지한다. 호출 여부를 검증하면서 필요할 때만 구현을 대체할 수 있다.
감시만 할 때:
```ts
const calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
};
describe("spyOn 기본 감시", () => {
test("원본 메서드 호출 감시", () => {
const addSpy = jest.spyOn(calculator, "add");
const result = calculator.add(2, 3);
expect(result).toBe(5); // 원본 메서드가 실행됨
expect(addSpy).toHaveBeenCalledWith(2, 3);
expect(addSpy).toHaveBeenCalledTimes(1);
addSpy.mockRestore(); // 원본 복원
});
});
```
모킹할 때:
```ts
describe("spyOn 구현 대체", () => {
let consoleSpy;
beforeEach(() => {
consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {});
});
afterEach(() => {
consoleSpy.mockRestore(); // 원본 console.log 복원
});
test("console.log 출력 제어", () => {
console.log("이 메시지는 출력되지 않음");
expect(consoleSpy).toHaveBeenCalledWith("이 메시지는 출력되지 않음");
});
});
```
## fn vs spyOn
| 구분 | `jest.fn()` | `jest.spyOn()` |
| ---------------- | ---------------------------- | ---------------------------------- |
| 목적 | 새로운 mock 함수를 생성할 때 | 기존 메서드를 감시하거나 대체할 때 |
| 원본 구현 | 없음 (완전히 새로 구현한다) | 기본적으로는 유지된다 |
| 복원 | 불가능 (복원할 상태가 없다) | `mockRestore()` |
| 언제 사용하는가? | 의존성을 완전히 제거할 때 | 메서드 호출을 감시할 때 |
> [!note] 개인적인 선택 기준
>
> - 외부 라이브러리의 경우 : jest.fn()
> - 내부 함수의 경우 : jest.spyOn()
함수의 내부 구현이 어떤 쪽에서든 의미가 있다면 `jest.spyOn`을 쓰게 되는 것 같다.
## jest.mock vs spyOn
`jest.mock()`은 모듈 전체 또는 특정 export의 반환값을 테스트용으로 대체할 때 사용한다.
`jest.spyOn()`은 기존 객체의 메서드 호출을 감시하거나, 그 메서드의 구현을 일시적으로 바꿀 때 사용한다.
| 구분 | `jest.mock()` | `jest.spyOn()` |
| --- | --- | --- |
| 목적 | 모듈 또는 export를 테스트용 구현으로 대체 | 기존 객체의 메서드를 감시하거나 대체 |
| 제어 범위 | import되는 모듈 단위 | 이미 존재하는 객체의 특정 메서드 |
| 원본 구현 | 기본적으로 대체됨 | 기본적으로 유지됨 |
| 반환값 제어 | 모듈 export의 반환값을 통째로 제어하기 좋음 | 특정 메서드의 반환값을 제어하기 좋음 |
| 복원 | mock 해제나 모듈 리셋이 필요 | `mockRestore()` 사용 |
훅이나 모듈 export처럼 테스트 대상 코드가 import해서 사용하는 의존성의 반환값을 통째로 바꿔야 한다면 `jest.mock()`이 더 자연스럽다.
반대로 이미 가진 객체의 메서드가 호출되었는지 확인하거나, 원본 구현을 유지한 채 호출만 감시하고 싶다면 `jest.spyOn()`이 더 자연스럽다.
## mockImplementation()
> mock 함수의 구현체를 직접 정의하기
```ts
const mockFn = jest.fn();
mockFn.mockImplementation(() => "mocked value");
expect(mockFn()).toBe("mocked value");
```
## mockReturnValue()
> mock 함수의 반환값을 직접 정의하기
```ts
const mockFn = jest.fn();
mockFn.mockReturnValue("mocked value");
expect(mockFn()).toBe("mocked value");
```
## 관련 문서
- [[Jest Cleanup]]
- [[Jest Matcher]]
- [[React Testing Library 에러 해결 노트]]