Mocking
- 실제값이 아닌 가짜값을 주입하여 이를 통한 테스트 환경을 통제하여 의존성의 독립을 가져온다.
- import 를 통해 외부 모듈을 값이나 로직을 수정함으로 인해 모듈 내의 동작이 의도에 맞게 동작하는지 검토할 수 있다
(외부 환경 요인에 대한 적절한 통제)
- Mocking은 제어할 수 없는 것을 대체할 수 있다.
Jest mocking
- 의존성을 Mock function으로 대체
- Jest의 mock 함수는 함수 호출 기록, 반환값 설정, 모듈 구현 내용 변경에 대한 기능을 가진다.
1. Mock함수를 만들고 호출 캡쳐
const mockFn = jest.fn();
expect(mockFn).toHaveBeenCalledTimes(3) // mock 함수는 3번 호출됨
// 혹은
expect(mockFn.mock.calls).toHaveLength(3) // mock 함수 호출 history의 length
expect(mockFn.mock.calls[0]).toHaveLength(0) // 첫번쨰 호출의 argument length = 0
expect(mockFn.mock.calls[1][0]).toBe('foo') // 두번째 호출의 argument 검증
expect(mockFn.mock.calls[2][0]).toMatchObject(
expect.objectContaining({
foo: 'foo',
bar: 'bar'
})
) // 세번째 함수 호출의 argument 체크
2. 함수 반환값 설정
test("Return value of mock function", () => {
const mockFn = jest.fn()
// mock function의 return value를 설정하지 않았으므로 undefined
expect(mockFn()).toBeUndefined()
mockFn.mockReturnValue(1)
expect(mockFn()).toBe(1) // mock function의 return value는 1
mockFn.mockReturnValueOnce(2)
expect(mockFn()).toBe(2) // mock function의 return value는 1
// return value는 1, Once로 설정된 값이 한번 반환된 이후에는
// 이전의 반환 값을 다시 반환한다
expect(mockFn()).toBe(1)
})
체이닝
test("Return value of chained mock function", () => {
const mockFn = jest.fn()
mockFn
.mockReturnValueOnce(1)
.mockReturnValueOnce(2)
.mockReturnValueOnce(3)
.mockReturnValueOnce(4)
expect(mockFn()).toBe(1)
expect(mockFn()).toBe(2)
expect(mockFn()).toBe(3)
expect(mockFn()).toBe(4)
expect(mockFn()).toBe(undefined)
})
3. 함수 내부 구현 수정 (기존 모듈의 구현 변경)
test("mock Implementation", () => {
const mockFn = jest.fn()
mockFn.mockImplementation((a: boolean) => !a)
expect(mockFn(true)).toBe(false)
})
test("mock Implementation once", () => {
const mockFn = jest.fn()
mockFn.mockImplementationOnce((a: boolean) => !a)
expect(mockFn(true)).toBe(false)
// 위에서 구현체가 한번 사용, 이후 초기화 (undefined 반환)
expect(mockFn(true)).toBe(undefined)
})
모듈
- 개발하는 프로덕트의 크기가 커질수록 파일 하나에서 구현을 관리하기엔 어려움이 따른다.
파일을 기능별, 목적별로 여러 개의 파일로 분리해야 한다. 이때 분리된 각각의 파일을 모듈이라고 부른다.
모듈 하나는 클래스 하나, 여러 개의 함수 객체로 구성된 라이브러리로 구성된다.
모듈이나 함수를 mocking 하기 위해서는 다음 세가지 함수를 사용할 수 있다.
1. jest.fn
- 모듈 내의 함수를 mocking할 때 사용한다. 하지만, Babel 사용시에는 테스트 시에 활용할 수 없다. (읽기 전용임)
- 위와 같은 상황에서는 외부 모듈을 테스트 하고자 할 떄에는 모듈 내의 일부 함수만 mocking 하는 것이 아니라
모듈 전체를 mocking 할 수 있도록 해야 함
2. jest.mock
- 모듈 전체를 mocking ✅✅
jest.mock(moduleName, factory, options)
import * as calculator from '../utils/calculator'
jest.mock('../utils/calculator');
const doAdd = (a: number, b: number) => {
return calculator.add(a, b)
}
test("Mock function", () => {
const result = doAdd(1, 2)
expect(calculator.add).toHaveBeenCalledWith(1, 2)
expect(result).toBeUndefined() // add 함수에 대한 mock 구현체가 없다. , 테스트 결과 정상
})
팩토리를 만들어서 정의, add 함수 재정의
jest.mock('../utils/calculator', () => {
return {
__esModule: true, // default export 사용시에 필요
default: jest.fn().mockImplementation(() => true),
add: jest.fn().mockImplementation((a: number, b: number) => a - b),
}
});
재정의하지 않은 다른 함수 같은 경우에는 정상적으로 호출되지 않음
3. jest.spyOn
- 모듈 내 특정 함수를 mocking 하거나 지켜볼 때 사용
- 함수의 사용은 지켜보지만, 해당 함수의 수행되는 결과는 원래 구현되어있던 원형을 따른다. 혹은 별도로 mocking을 지정
- 지정된 mocking을 제거할 수 있다.
4. jest.mocked
- typescript 사용 시에 type에 대한 적용
- mocked module에 대해서도 타입을 적용할 수 있다.
test("typed Mock function", () => {
// 혹은 `jest.Mocked<Source>` 를 이용
// calculator 모듈을 Mocked를 이용하여 wrapping
const mockedCalculator = calculator as jest.Mocked<typeof calculator>
// 기존의 add 함수가 가지는 타입을 맞춰야 한다.
mockedCalculator.add.mockReturnValue(0)
expect(calculator.add(1,2)).toBe(0)
})
'개발공부 > React' 카테고리의 다른 글
[React] CRA 없이 리액트 앱 만들기: Webpack 설정하기 (4) | 2024.10.29 |
---|---|
[React] CRA 없이 리액트 앱 만들기: 목적과 프로젝트 세팅 (4) | 2024.10.12 |
[React] 테스팅 패턴 - 모의 데이터를 이용하여 검증 (0) | 2024.07.27 |
[React] 테스팅 패턴 - Rendering 검증 (0) | 2024.07.21 |
[React] 테스팅 패턴 - AAA 패턴 (0) | 2024.07.02 |