-
Notifications
You must be signed in to change notification settings - Fork 0
NestJS에서 단위 테스트를 해보자!
Dohyeon Han edited this page Dec 5, 2022
·
1 revision
- 우리가 짠 코드가 원하는 대로 돌아가는지 어떻게 확인 할 수 있을까요? 가장 쉽게 떠올릴 수 있는 방법은 직접 실행 시켜서 확인해보는 것입니다. 하지만 새로운 기능이 생기고 리팩토링이 될 때마다 같은 테스트를 해 볼 수는 없는 노릇입니다. 그럴 때 사용하는 것이 테스트 코드이고 여러 테스트 방법 중 유닛 테스트는 함수 단위로 테스트를 실행하는 것입니다.
@Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository
) {}
...
// 유저 전체 조회
async findAll(): Promise<User[]> {
return await this.userRepository.findAll();
}
...
}
- 현재 프로젝트 코드의 일부입니다.
- findAll() 함수는 userRepository라는 인스턴스에 의존하고 있습니다.
- 함수 단위로 테스트를 하기 위해서는 이런 의존성을 끊어야 합니다.
- 이럴 때 사용할 수 있는 것이 바로 mocking입니다. 즉 가짜 userRepository를 주입해줘서 사용한다는 뜻입니다.
- 어떤 함수의 동작을 감시할 때 사용합니다.
- mocking된 함수, 진짜 함수 모두에 spy를 적용할 수 있습니다.
- spy를 통해 인자값, 반환값 등을 테스트할 수 있습니다.
nest g res user
라는 명령어를 실행하면 아래와 같은 user.service.spec.ts라는 파일이 생성됩니다.
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UserService],
}).compile();
service = module.get<UserService>(UserService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
- 테스트를 시 절대 경로를 읽을 수 없는 오류가 있을 수 있기 때문에 package.json의 "jest"에 해당 코드를 추가해줍니다.
"jest": {
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/$1"
},
}
- UserRepository를 주입 받아 findAll()을 작성한 후 해당 테스트를 실행해보면 실패가 발생합니다. 그 이유는 UserService는 UserRepository를 주입 받아 사용하고 있는데, 주입을 해주지 않았기 때문입니다.
- 테스트 방식 중에 mock과 spy가 있는데, 두 가지를 적용해보겠습니다.
- 먼저 주입할 repository 객체를 만듭니다.
- 이런 가짜 객체를 mock 객체라고 합니다.
const mockUserRepository = {
findAll: jest.fn(),
};
- 여기서 jest.fn은 가짜 함수를 생성하기 위한 것이고, 이것을 통해 임의로 반환 값을 정할 수 있습니다.
- providers에 주입 받을 타입과 실제로 주입할 객체를 명시해줍니다.
providers: [
UserService,
{
provide: UserRepository, // 주입할 타입
useValue: mockUserRepository, // 실제로 주입할 객체
},
],
- 이제 테스트 코드를 작성해 봅시다.
it('mock repository로 유저 전체 조회', () => {
const userStub: User = {
authProvider: 'google',
authId: '12345',
email: '[email protected]',
username: 'abcde',
_id: new Types.ObjectId('637f8ec80b9f9e762f47c269'),
createdAt: '',
updatedAt: '',
};
const users = [userStub];
mockUserRepository.findAll.mockResolvedValue(users); // 비동기로 findAll의 반환값 지정
service.findAll().then((result) => {
expect(result).toEqual(users); // result의 결과가 users와 같으면 통과
});
});
- 해당 테스트를 수행하면 통과하는 것을 볼 수 있습니다.
- 전체 코드
import { Types } from 'mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { User } from './entities/user.entity';
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
const mockUserRepository = {
findAll: jest.fn(),
};
const userStub: User = {
authProvider: 'google',
authId: '12345',
email: '[email protected]',
username: 'abcde',
_id: new Types.ObjectId('637f8ec80b9f9e762f47c269'),
createdAt: '',
updatedAt: '',
};
describe('UserService', () => {
let service: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: UserRepository,
useValue: mockUserRepository,
},
],
}).compile();
service = module.get<UserService>(UserService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('mock repository로 유저 전체 조회', () => {
const users = [userStub];
mockUserRepository.findAll.mockResolvedValue(users);
service.findAll().then((result) => {
expect(result).toEqual(users);
});
});
});
- 기존 코드에 spy를 추가해보겠습니다.
- spy는 어떤 메소드를 감시하는 기능입니다. spy를 적용하여 해당 메소드가 얼마나 호출 되었는 지, 어떤 값이 전달되었는 지를 확인할 수 있습니다.
it('spy를 적용한 mock repository', () => {
const users = [userStub];
const spy = jest.spyOn(service, 'findAll');
mockUserRepository.findAll.mockResolvedValue(users); // 비동기로 전체 유저 조회
service.findAll().then((result) => {
expect(result).toEqual(users);
});
});
- 이제 spy를 통해 findAll 함수를 감시할 수 있습니다.
- spy의 값을 테스트해보겠습니다.
it('spy를 적용한 mock repository', () => {
const users = [userStub];
const spy = jest.spyOn(service, 'findAll');
mockUserRepository.findAll.mockResolvedValue(users); // 비동기로 전체 유저 조회
service.findAll().then((result) => {
expect(result).toEqual(users);
expect(spy).toBeCalledTimes(1); // 함수는 1번 호출
expect(spy).toBeCalledWith(); // 빈 값이 전달
});
- 해당 함수를 테스트하면 통과하는 것을 볼 수 있습니다.
- 📃 기획서
- 📂 Backlog
- 📊 ERD, 폴더 구조
- 🗓️ 회의록