Integration Test 설정
`Integration Test`는 `API` 부터 반환 되는 결과까지 검증하는 테스트로 통합 테스트와 같은 의미로 사용되고 있습니다. `Nest.js`는 `supertest` 모듈을 사용한 테스트를 진행하고 있습니다.
let app: INestApplication;
let authService: AuthService;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.compile();
authService = moduleFixture.get<AuthService>(AuthService);
app = moduleFixture.createNestApplication();
await app.init();
});
`INestApplication`을 통해 `Test Server`를 만들게 되면 `main.ts` 설정 파일을 제외한 값들이 적용되게 됨으로써 간단한 테스트 서버를 만들 수 있게 되어집니다. 이제 실제로 서비스에서 사용되는 `AppModule`을 `import`를 해주게 되면 서비스에 적용할 코드들을 실제로 사용할 수 있게 되어집니다.
supertest를 이용한 테스트
테스트를 통해 코드들을 검증할 단계입니다. 여기서 참고 해야 하는 것은 위에서 말을 했듯이 `main.ts`에 적용한 코드들은 더 이상 적용이 되지 않고 있기 때문에 `globalPrefix`를 적용 받지 않는 다는 것을 알아야 합니다.
아래는 `main.ts` 내에 `app` 설정 시 지정한 `prefix`입니다.
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
하지만 더이상 `globalPrefix`는 필요 없기 때문에 `prefix`를 제외한 주소로 테스트를 해야 합니다. 다음은 회원가입 서비스를 검증하기 위한 테스트 코드입니다.
it('should be succesed with 201', () => {
return request(app.getHttpServer())
.post('/auth/v1/signup')
.send(user)
.expect(201);
});
`.post()`는 `method`를 지정하고, `.send()`는 `request body`에 넘겨져야 하는 값을 입력하는 메소드입니다. 마지막 `.expect()`는 어떠한 결과 값이 예상될 때 사용하는 메소드입니다. 이미 기본적인 `jest`를 사용할 수 있으신 분들이라면 금방 눈에 들어 오실 거라고 생각합니다.
Mocking을 사용해야 했던 상황
`Integration Test`를 진행하면서 `Redis`에 대한 부분이 제대로 주입이 되지 않고 있는 문제가 발생하였습니다. `AppModule` 을 `import`를 하면 자연스럽게 `Reids` 설정을 입력 받게 되는데, 여기서 `TTL(Time To Live)`에 입력되는 값이 `integer`가 아니라는 오류가 발생했습니다.
[ErrorReply: ERR value is not an integer or out of range
분명 기존에 `API` 테스트와 유닛 테스트를 진행 할 때는 문제없이 수행되던 코드 였으나, 이상하게도 `Integration` 테스트를 하면서 제대로 진행되지 않는 문제가 발생하여 `Mocking`을 사용하여 처리하는 방식으로 수행 하게 되었습니다.
`Mocking`을 사용하여도 무방하다고 생각한 이유는 실제 테스트와 유닛 테스트를 통해 이미 검증 받은 코드이기에 진행 할 수 있었던 부분이었습니다. 여기서 말하고 싶은 것은 무조건적인 `Mocking` 사용은 피하셔야 한다고 말씀드리고 싶습니다.
Mocking
테스트코드를 구현하고 있는 서비스는 `AuthService`에 `CACHE_MANAGER`를 주입을 받고 있습니다.
constructor(
private userRepository: UserRepository,
@Inject(CACHE_MANAGER) private cacheManager: Cache,
private jwtService: JwtService,
) {}
그렇기 때문에 `AuthModule`에 주입이 되는 `Cache`를 `mocking` 하여 주입을 하면 원하는 값이 나오는 메소드로 변경이 가능하게 됩니다.
@Module({
imports: [
CacheModule.register<RedisClientOptions>({ // 해당 부분을 변경
store: redisStore as any,
url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
}),
],
우선 먼저 `mocking`하는 객체를 생성하여 기존 `Provider`에 주입을 하는 코드를 보도록 하겠습니다.
let app: INestApplication;
let userRepository: UserRepository;
let authService: AuthService;
const mockCacheManager = {
set: jest.fn(),
get: jest.fn(),
};
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(CACHE_MANAGER)
.useValue(mockCacheManager)
.compile();
userRepository = moduleFixture.get<UserRepository>(UserRepository);
authService = moduleFixture.get<AuthService>(AuthService);
app = moduleFixture.createNestApplication();
await app.init();
});
`mockCacheManager` 라는 객체를 생성하고 거기에 사용되는 `set`, `get` 함수를 `mock` 함수 처리를 하였습니다. 이후에, `overrideProvider()` 함수를 사용하여 `CACHE_MANAGER`에 덮어 씌워서 사용할 수 있게 하였습니다.
테스트 코드에 실제로 사용하고자 할때, 아래 코드와 같이 결과값을 임의로 입력하게 하면 원하는 값이 나오게 하는 코드를 작성 할 수 있게 됩니다.
mockCacheManager.set.mockReturnValueOnce(true);
이러한 방식 외에도 `spyOn` 방식을 사용하여 `Mocking`하거나 `jestMock`을 사용하여 `Mocking` 하는 방식이 존재합니다. `Provider`를 `override` 하는 방식 외에 나머지 코드는 해당 글에서 다루지는 않겠습니만, 기본적인 의존성 주입 방식에 대한 이해도를 높아졌을 때, `Mocking`을 하는 방식을 사용하는 것을 추천합니다.
'Backend > Nest.js' 카테고리의 다른 글
SSE(Server-Sent Event) (0) | 2024.02.02 |
---|---|
Nest.js Bull (0) | 2023.12.05 |
Nest.js mongodb-memory-server, cache 적용 (1) | 2023.10.05 |
Nest.js Rate Limiting 설정 (0) | 2023.09.30 |
Nest.js Swagger 보안 설정 (0) | 2023.09.30 |