https://velog.io/@cjstkrak/About-Jest
nestjs 에서는 jest를 기본 테스트 라이브러리로 사용한다
따로 설치해 줄 필요가 없고, controller나 service를 커맨드로 생성하면
자동으로 .spec.ts 를 만들고, 이를 찾아서 테스트를 진행한다.
jest": {
...
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/$1"
}
}
기본적으로 절대 주소를 찾지 못한다.
똑같은 환경을 구성하는 와중에 오류가 같이 발생하여 많이 헤매었다..
유의하자
어떤 환경에서 테스트하든지, 테스트 대상과 같은 환경을 만들어 주는 것은 중요하지만, NestJS에서는 더더욱 중요하다.
일반적으로 NestJS에서 테스트 할 때 어떻게 dependency들을 inject하여 같은 환경을 만들어 줄 수 있는지에 대해서는 이미 자세히 작성한 링크를 참고하자.
type MockRepository<T = any> =
Partial<Record<keyof Repository<T>, jest.Mock>>;
Mocking한 Repository의 타입을 지정해주기 위함이다.
Record를 통해 모든 타입을 mocking한 뒤 partial을 통해 부분적으로 사용할 수 있게 만들어 주었다.
auth.service.spec.ts
type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;
// 모든타입을 mocking 한 뒤 부분적으로 사용하기 위한 타입
const mockRepository = () => ({
createQueryBuilder: jest.fn().mockReturnValue({
select: jest.fn().mockReturnThis(),
// 체이닝함수를 리턴할 때 this를 리턴하게 되는 함수이다
getOne: jest.fn().mockReturnThis(),
delete: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
set: jest.fn().mockReturnThis(),
save: jest.fn().mockReturnThis(),
getMany: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
}),
save: jest.fn(),
sign: jest.fn(() => 'token'),
});
const mockConfigService = {
get: jest.fn(),
};
우리는 단순한 Unit Test를 하는 것이고, 실제 DB에 영향을 주고 싶지 않다. 이럴 때 getRepositoryToken
을 사용하면,
@InjectRepository(UserEntity)
가 리턴하는 값과 같은 injection token을 가질 수 있고, 이를 통해 우리는 실제 DB와 연결하지 않고도,
테스트 대상과 같은 환경을 만들어 줄 수 있는 것이다.
describe('AuthService', () => {
let service: AuthService;
let jwtService: JwtService;
let configService: ConfigService;
let userRepo: MockRepository<UserRepository>; // mockRepository
let accessTokenRepo: MockRepository<AccessTokenRepository>;
let refreshTokenRepo: MockRepository<RefreshTokenRepository>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
UserRepository,
AccessTokenRepository,
RefreshTokenRepository,
{ provide: JwtService, useValue: mockRepository() },
// JWT를 DI 해주고 value는 내가 설정한 모의객체를 사용한다.
{ provide: ConfigService, useValue: mockConfigService },
{
provide: getRepositoryToken(UserEntity),
useValue: mockRepository(),
},
{
provide: getRepositoryToken(AccessTokenEntity),
useValue: mockRepository(),
},
{
provide: getRepositoryToken(RefreshTokenEntity),
useValue: mockRepository(),
},
],
}).compile();
service = module.get<AuthService>(AuthService);
userRepo = module.get<MockRepository<UserRepository>>(
getRepositoryToken(UserEntity),
);
accessTokenRepo = module.get<MockRepository<AccessTokenRepository>>(
getRepositoryToken(AccessTokenEntity),
);
refreshTokenRepo = module.get<MockRepository<RefreshTokenRepository>>(
getRepositoryToken(RefreshTokenEntity),
);
jwtService = module.get<JwtService>(JwtService);
configService = module.get<ConfigService>(ConfigService);
});
여기서 생긴 문제점은 getRepositoryToken(UserEntity)
이 실제 환경과 맞지 않는다는 오류가 발생했다.
이유는 db와 연결된 로직을 repository.ts
에 분리했었고,
repository파일 class를 DI했으니 RepositoryToken과는 다른 개념이었다.
글을 쓰다가 추가로 알게 된 사실이
굳이 db와 연결된 함수들을 mocking할 필요가 있을까? 라는 생각이 들었고
{
provide: getRepositoryToken(UserEntity),
useValue: mockRepository(),
},
이런 코드 필요없이
const mockUserRepository = {
findUserByEmail: jest.fn(),
};
{ provide: UserRepository, useValue: mockUserRepository },
까지만 해도 service를 test하는거니 상관없었다.
이제 살짝 mock에 대한 개념과 활용법을 알게되었고
테스트를 하며 어떤식으로 접근하고 생각해야
완전한 테스팅이고, 어떤 규칙과 절차를 따라야하는지 고민이 됐다.
이 부분은 다른코드를 많이보고, tdd개발방법론에 대한 공부가 필수라고 생각이 된다.
https://marklee1117.tistory.com/6
https://velog.io/@hkja0111/NestJS-11-Unit-Test-QueryBuilder
https://dev.to/shubham_kadam/nestjs-mocking-databases-for-efficient-tests-3efl
https://dev.to/dylanju/jest-mocks-18l9
https://velog.io/@wngud4950/NestJS%EB%A1%9C-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0feat.-jest
https://blogshine.tistory.com/186 // 테스트코드작성 규칙