Nest.js Integration Test 와 Mocking

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