[Jest] 다양한 Mocking 방법
author: Dev.ian
createdAt: 2024-05-27
updatedAt: 2024-05-28
Intro
유닛테스트를 할 때 자주 사용하는 jest 를 이용해 mocking 하는 법이 아직 서툴다.
적어도 데이터를 변경하는 기능 (put, update, delete) 은 반드시 유닛테스트를 만들어 두려고 하는 편인데 mocking 이 서툴다보니 시간이 너무 오래걸린다.
jest 의 mocking 관련 메소드, private 메소드의 mocking 방법 등을 잘 정리해두어 활용하고자 한다.
Contents
- Nest.js 프로젝트
- Jest 로 Test 하려는 Method 들은 아래의 UserService 에 있다.
export class UserService {
async fetchUser(id: string): Promise<User> {
// check parameter
if (!id) {
throw new Error('no exist id');
}
// return
return this.getUSer(id);
}
// === private ===
private async getUser(id: string): Promise<User> {
const user: User = await db.get(id);
if (!user) {
return null;
}
return user;
}
}
- 알맞는 Error 를 던지는가?
- Remark 의
UserSerivce.fetchUser
메소드는 파라미터인 id 가 없다면 에러를 던진다. - fetchUser 메소드가 알맞는 에러를 던지는지 테스트한다.
toThrowError
- Test code 를 작성할 때, 비동기처리인 경우 Test 의 결과가 Promise 이기 때문에
await
와rejects
orresolves
를 이용해 작성해야 한다.
it('error: no exist id', async () =>{
// given
const id = 'test_id';
const testFunc = async () => {
await fetchUser(id);
};
// when & then
await expect(() => testFunc()).rejects.toThrowError(new Error('no exist id'));
}, 999_999_999);
or
it('error: no exist id', async () =>{
// given
const id = 'test_id';
// when & then
await expect(fetchUser(id)).rejects.toThrowError(new Error('no exist id'));
}, 999_999_999);
- Private function mocking
- Remark 의
UserSerivce.getUser
메소드는 private 이다. - 경우에 따라서는 Class 의 private 메소드가 아직 구현되지 않았거나 다른 이유들로 인해서 Mocking 이 필요한 경우가 있다.
spyOn
jest.spyOn
함수는 Mocking 용 함수나 값을 만들어준다.- private 메소드가 있는 class 를 any 타입으로 형 변환 하여 mocking 메소드를 만들어준다.
toHaveBeenCalled
나toHaveBeenCalledTimes
를 이용해 mocking 한 메소드가 제대로 호출되었는지 확인할 수 있다.
describe('UserServiceSpec', () => {
let user: UserService;
let order: OrderService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UserService, OrderService],
imports: [UserModule],
}).compile();
user = module.get<UserService>(UserService);
order = module.get<OrderService>(OrderService);
});
it('should be defined', () => {
expect(user).toBeDefined();
expect(order).toBeDefined();
});
it('mocking private method', async () =>{
// given
const id = 'test_id';
const getUser = jest
.spyOn(user as any, 'getUser')
.mockImplementation(async () => {
return {id: 'mock-user'};
});
// when & then
expect(getUSer).toHaveBeenCalled();
expect(getUSer).toHaveBeenCalledTimes(1);
}, 999_999_999);
});
- Class Mocking
- Remark 의
UserService
class 를 상속받아 Class 를 Mocking 할수도 있다. - UserService 의 메소드나 생성자등을 오버라이딩하는 형태로 Mocking 한다.
overrideProvider
- Mocking 한 Class 를 Jest 에서 사용하기 위해서는 Mocking 한 Class 가 있는 Module 에서 오버라이드를 해야한다.
- 오버라이드하려는 Class 를 대신해 Mocking 한 Class 를 지정해주면 Jest 에서 Mocking 한 Class 를 사용할 수 있다.
class MockUserService extends UserService {
async fetchUser(id: string): Promise<User> {
if (!id) {
throw new Error('no exist id');
}
return {
id: 'mock-user-id',
};
}
}
describe('UserServiceSpec', () => {
let user: UserService;
let order: OrderService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MockUserService, OrderService],
imports: [UserModule],
})
.overrideProvider(UserService)
.useClass(MockUserService)
.compile();
user = module.get<UserService>(UserService);
order = module.get<OrderService>(OrderService);
});
it('should be defined', () => {
expect(user).toBeDefined();
expect(order).toBeDefined();
})
});
- Object 비교
- 객체의 모든 키와 값이 예상한 값과 동일한지 비교할 수 있다.
toStrictEqual
it('Object: equal', async () =>{
// given
const id = 'test_id';
// when
const user = await fetchUser(id);
// then
await expect(user).toStrictEqual({id: 'strict-user-id'});
}, 999_999_999);