최근 통합테스트를 수행하며, 핵심 메소드에 대한 깊은 이해의 필요성을 느꼈습니다.
통합테스트를 보다 효과적으로 수행하기 위해, Mock, SpyOn, 그리고 Fn의 개념과 사용법을 명확히 파악하는 것이 중요하다고 생각했습니다.
이 세 가지 메소드는 테스트 코드의 각기 다른 요구사항을 충족시키기 위해 사용되며, 각각의 목적과 사용 상황을 이해하는 것은 테스트의 효율성과 정확성을 크게 향상시킬 수 있습니다.
본 정리에서는 통합테스트의 핵심 요소로서 mock
, spyOn
, fn
의 개념과 사용 목적에 대해 살펴보고, 실제 적용 예시를 통해 이들 메소드의 적절한 사용 상황을 비교 분석합니다.
이 내용은 주로 Jest와 Vitest의 문서를 참조하여 정리했으며, 설명은 Jest를 중심으로 진행됩니다.
이 글의 주 목적은 통합테스트의 전반적인 설명보다는 Mock, SpyOn, Fn이라는 세 가지 주요 메소드의 차이점과 적절한 사용 상황을 이해함으로써, 개발자가 상황에 맞게 가장 적합한 도구를 선택하여 사용할 수 있도록 하는 데 있습니다.
중요한 점은 통합 테스트가 개별 유닛의 내부 로직보다는 유닛들 간의 연결과 상호작용의 정확성에 초점을 맞춘다는 것입니다.
이를 위해, 테스트 중인 유닛 외부의 요소들을 제어하거나 예측 가능한 상태로 만드는 것이 중요합니다.
이 과정에서 Spyon, Mock 그리고 Fn 같은 메소드를 사용하는데 이에 대해 설명하겠습니다.
spyOn
은 테스팅 프레임워크에서 제공하는 기능으로, 객체의 메서드 호출을 감시(spy)하고, 필요한 경우 메서드의 동작을 임시로 변경할 수 있게 해줍니다. 이를 통해 메서드가 호출되었는지, 어떤 인자로 호출되었는지 등을 테스트 코드 내에서 확인할 수 있습니다.
jest.spyOn(object, methodName);
object
: 감시하려는 메서드가 속한 객체methodName
: 감시하려는 메서드의 이름 (문자열 형태)spyOn으로 생성된 스파이 객체는 원래 함수를 대체하지 않고 감싸므로, 함수가 호출될 때마다 스파이 객체가 이를 감지하고 추적 정보를 기록합니다.
테스트 간 영향을 줄이기 위해 각 테스트 이후에 restoreAllMocks를 호출하여 모의 설정을 초기화하는 것이 좋습니다.
afterEach(() => {
jest.restoreAllMocks();
});
이제 직접 코드 예시를 보며 어떻게 적용하는지 살펴보겠습니다.
spyOn
을 사용하여 함수 호출을 감시하면서 원래의 기능을 유지하거나 필요에 따라 모의하는 것이 주요 특징입니다. 그러므로, fetchUserData
함수를 감시하되 원래의 동작을 유지하면서 호출 정보(예: 호출 횟수, 전달된 인자 등)를 추적하는 예시가 더 적합할 것 같습니다. 이를 통해 jest.spyOn
의 감시 기능을 보다 명확하게 드러낼 수 있습니다.
원래 함수를 유지하면서 함수 호출을 추적하고 검증하는 예시를 살펴보겠습니다.
원래 함수의 동작을 유지하면서 함수 호출 추적하기
원래 함수:
// user.js
export async function fetchUserData(userId) {
// 외부 API 호출을 가정
return { id: userId, name: 'John Doe' };
}
테스트 코드:
// user.test.js
import * as UserModule from './user';
describe('fetchUserData', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('tracks the call to fetchUserData while keeping its original implementation', async () => {
// fetchUserData 함수를 감시하지만 원래의 구현을 유지
const spy = jest.spyOn(UserModule, 'fetchUserData');
const userId = 'testId';
const userData = await UserModule.fetchUserData(userId);
// spyon 하고 있는 fetchUserData 함수가 호출되었는지 검증
expect(spy).toHaveBeenCalledWith(userId);
// 반환된 데이터가 원래 함수의 반환 값과 일치하는지 검증
expect(userData).toEqual({ id: userId, name: 'John Doe' });
});
});
이 예시에서는 jest.spyOn
을 사용하여 fetchUserData
함수를 감시하되, mockImplementation
을 통해 원래의 함수 구현을 유지합니다. 이 방식을 통해 함수가 올바르게 호출되었는지와 원래의 동작을 수행하는지를 동시에 검증할 수 있습니다. 이는 jest.spyOn
의 능력을 보여주는 좋은 예시이며, 실제 테스트에서 원하는 함수의 동작을 유지하면서 추가적인 검증을 수행하고자 할 때 유용합니다.
jest.spyOn의 주요 장점은 기존 함수의 동작을 변경하지 않고 호출을 감시하거나, 필요에 따라 동작을 모의할 수 있다는 점입니다.
mock
소개Jest의 jest.mock
함수는 통합 테스트를 수행할 때, 외부 라이브러리나 프로젝트 내 복잡한 유틸리티 모듈을 모의화하여 실제 구현 대신 가상의 구현을 사용함으로써 테스트의 복잡성을 줄이고 의존성을 제거할 수 있습니다. 이를 통해 테스트가 외부 요소에 의존하지 않고 일관되게 수행될 수 있도록 도와줍니다.
mock
의 기본 사용법jest.mock('moment'); // 외부 라이브러리 대체
jest.mock('./databaseQuery'); // 프로젝트 내 유틸 파일 대체
'moment'
는 외부 날짜 처리 라이브러리를 모의화합니다.'./databaseQuery'
는 프로젝트 내 데이터베이스 쿼리를 수행하는 유틸리티 파일을 모의화합니다.mock
의 주요 특징mock
을 사용한 실무 적용 예시moment
) 모의화// time.test.js
jest.mock('moment'); // moment 라이브러리를 모의화
import moment from 'moment';
// moment 함수에 대한 모의 구현 정의
moment.mockReturnValue('2024-02-27'); // 테스트 목적의 특정 날짜를 반환
// 실제 비즈니스 로직에서 moment를 호출하고 모의 날짜가 반환되는지 검증
function getCurrentDate() {
return moment().format('YYYY-MM-DD');
}
// getCurrentDate 함수가 모의 날짜 '2024-02-27'을 반환하는지 검증
test('getCurrentDate returns mocked date', () => {
expect(getCurrentDate()).toBe('2024-02-27');
});
이 예시에서는 moment
라이브러리를 모의화하여 테스트 중에는 항상 '2024-02-27'
이라는 특정 날짜를 반환하도록 설정했습니다. 이를 통해 날짜 관련 로직을 테스트할 때 실제 시간의 변화에 영향을 받지 않습니다.
databaseQuery
) 모의화// database.test.js
import { fetchData } from './databaseQuery';
// databaseQuery 파일을 모의화합니다.
jest.mock('./databaseQuery', () => ({
fetchData: jest.fn()
}));
// fetchData 함수에 대한 모의 구현 정의
fetchData.mockResolvedValue({ id: 1, name: 'Mocked Data' }); // 비동기 처리를 가정한 모의 데이터 반환
// fetchData를 사용하는 비즈니스 로직을 테스트하고, 모의 데이터가 반환되는지 검증
async function getData() {
return await fetchData();
}
// getData 함수가 모의 데이터를 정상적으로 반환하는지 검증
test('getData returns mocked data', async () => {
const data = await getData();
expect(data).toEqual({ id: 1, name: 'Mocked Data' });
});
이 예시에서는 데이터베이스 쿼리를 수행하는 fetchData
함수를 모의화하여 테스트 중에는 항상 { id: 1, name: 'Mocked Data' }
라는
특정 데이터를 반환하도록 설정했습니다. 이를 통해 데이터베이스와의 실제 상호작용 없이도 데이터 처리 로직을 검증할 수 있습니다.
fn
소개fn()
메서드는 함수의 모의 구현을 생성하여, 함수 호출을 추적하고 그 동작을 제어할 수 있게 합니다. 이는 모듈 전체를 모킹할 필요 없이 특정 함수만을 대상으로 할 때 매우 유용합니다.
const mockFunction = jest.fn();
이 코드는 Jest를 사용해 모의 함수를 생성하며, 이 함수는 호출 시 그 정보를 추적하고 원하는 대로 동작을 설정할 수 있습니다.
API 호출 함수를 모킹하고 이를 활용하는 서비스 함수를 테스트하는 예시를 들어보겠습니다.
fetchUser
: 사용자 정보를 서버로부터 가져오는 API 호출 함수입니다.getUserProfile
: fetchUser
를 호출해 사용자 프로필 정보를 처리하는 서비스 함수입니다.api.js
: fetchUser
와 같은 API 호출 함수들이 정의된 파일입니다.userService.js
: 사용자 관련 서비스 로직을 처리하는 파일로, api.js
의 fetchUser
함수를 사용합니다.userService.js
// userService.js
import { fetchUser } from './api';
export async function getUserProfile(userId) {
const user = await fetchUser(userId);
// 사용자 프로필 정보 처리 로직
return user;
}
userService.test.js
)jest.fn()
을 사용해 fetchUser
를 모킹하고 getUserProfile
함수를 테스트해 봅시다.
// userService.test.js
import { getUserProfile } from './userService';
import * as api from './api';
// `fetchUser` 함수 모킹
api.fetchUser = jest.fn();
describe('getUserProfile', () => {
it('successfully fetches user profile', async () => {
const mockUser = { id: '1', name: 'John Doe' }; // 모킹 데이터
// `fetchUser`가 모킹 데이터를 반환하도록 설정
api.fetchUser.mockResolvedValue(mockUser);
// `getUserProfile` 함수 호출 및 결과 검증
const userProfile = await getUserProfile('1');
expect(userProfile).toEqual(mockUser);
});
});
이 테스트는 jest.fn()
을 활용해 fetchUser
함수를 모킹하고, 이 함수가 예상대로 특정 사용자 데이터를 반환하도록 설정합니다. 이후 getUserProfile
함수를 호출해, 모킹된 fetchUser
를 통해 올바른 사용자 프로필 정보를 성공적으로 가져오는지 확인합니다.
이 예시는 jest.fn()
을 이용해 의존성을 모킹하고, 복잡한 외부 시스템이나 API 호출 없이 함수의 동작을 효과적으로 테스트할 수 있는 방법을 보여줍니다.
테스트 프레임워크에서 제공하는 다양한 목킹 기능을 통해 테스트의 복잡성을 줄이고, 의존성을 효과적으로 관리할 수 있도록 도와줍니다.
이러한 기능들은 각각의 사용 목적과 주 사용 경우가 있으며, 필요에 따라 서로 조합하여 사용할 수 있습니다.
기능 | jest.mock() | jest.fn() | jest.spyOn() |
---|---|---|---|
사용 목적 | 모듈 전체를 모킹 | 개별 함수를 모킹 | 기존 함수의 호출을 추적하며 모킹 가능 |
주 사용 경우 | 모듈 내 여러 함수/변수를 모킹할 때 | 단일 함수의 동작을 커스터마이즈할 때 | 기존 함수의 동작을 유지하면서 감시할 때 |
특징 | 모듈 전체를 자동으로 모킹 함수로 대체 | 생성된 모킹 함수를 직접 조작 및 설정 | 기존 함수를 유지하며 함수 호출을 추적 |
반환값/예외 | 모듈의 함수들을 모킹하여 커스텀 가능 | 모킹 함수의 반환값/예외를 커스텀 가능 | 함수의 반환값을 모킹하거나 함수 호출을 가로채어 커스텀 가능 |
사용 예 | 여러 외부 API 함수를 포함한 모듈 모킹 | 이벤트 핸들러, 콜백 함수 모킹 | 함수가 어떻게 호출되는지 감시하면서 테스트 |
출처:
https://jestjs.io/docs/jest-object#jestspyonobject-methodname
https://jestjs.io/docs/mock-function-api
https://github.com/tinylibs/tinyspy
https://vitest.dev/guide/mocking.html
https://vitest.dev/api/vi#vi-spyon
https://vitest.dev/api/vi#vi-fn