Nest CLI로 Controller를 생성하면 다음과 같은 테스트 코드를 기본적으로 생성해준다.
// users.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
describe('UsersController', () => {
let usersController: UsersController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile();
controller = app.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
위 코드에선 "UsersController" 라는 test에서 우선 beforeEach
문 안에 테스트를 위한 TestingModule 이라는 것을 만들고 있다. 아래는 실제로 서비스를 구현하는 코드에서 UsersModule 클래스를 생성하는 코드이다.
// users.module.ts
// 여러 import 구문들 ...
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
“위의 테스트를 위한 users module을 생성하는 코드”와 “아래의 실제 users module class를 데코레이터로 정의하는 부분”은 닮아있다는 것을 알 수 있다.
근데 import나 providers 가 완전히 같지 않다! 그 이유는 테스트를 할 땐 실제로 해당 컨트롤러를 테스트 하는데 필요한 의존성만 최소한으로 기술하기 때문이다. 아래 코드를 보면 UsersController는 UsersService에만 의존하고 있다. 따라서 테스트 코드는 저렇게만 작성해도 충분하다! 모듈을 최대한 독립적으로 만들어서 테스트한다.
// users.service.ts
// ...
@Controller()
export class UsersController {
constructor(private readonly usersService: UsersService) {}
...
}
근데 Unit Testing을 할 때는 이런 걸 좀 더 제한해야 할 필요가 있다. 만약 위 예시에서 UsersService가 UsersRepository에 의존적이라면? UsersRepository도 providers로 기술해줘야 한다. 그러면 규모가 점점 커져서 Unit testing과는 거리가 멀어질 수 있다.
지금은 하나의 Controller만을 Unit Test 하려는 것이기 때문에 사실 UsersService조차 없이 독립적으로 하는 편이 좋다. 이렇게 의존성을 분리하고 싶다면 아래와 같이 overrideProvider 함수를 사용하면 된다. override~
종류의 함수들과 use~
종류의 함수들을 이용해 우리가 테스트하려는 부분을 제외하고는 mocking 등을 통해 제공해줌으로써 더 독립적인 테스트를 할 수 있게 된다.
아래와 같이 코드를 작성하면, UsersService에는 우리가 구현한 실제 UsersService가 들어가는 게 아니라, mockUsersService가 들어가게 된다.
const mockUsersService = {};
// ...
const app: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
})
.overrideProvider(UsersService)
.useValue(mockUsersService)
.compile();
// ...
실제 우리가 구현한 UsersService를 사용해 UsersController를 테스트할거면 UsersService에 딸려있는 의존성들도 모두 명시해줘야한다. 근데 이러면 “Unit” Testing과는 거리가 멀어진다…
그리고 nestjs CLI 로 생성된 초기 테스트 코드는 해당 컨트롤러를 생성할 수 있는지에 대한 테스트인데, 만약 위와 같이 코드를 작성 했을 때 서비스 클래스가 또 다른 의존성(ex. UsersRepository)에 묶여있으면 테스트가 실패하게 된다. 따라서 저렇게 서비스로의 의존성을 분리해주는 편이 좋다고 한다. 이 기본 테스트인 객체 생성 가능 여부 테스트는 “테스트 대상의 의존성이 테스트 파일 내에서 제대로 구성돼있는지”를 확인하기 위한 테스트이다.