end to end testing

global middleware 옮기기

Testing

End to End test

  • 유닛 테스트보다 넓은 범위를 테스트

  • 전체 서비스의 복사본이나 인스턴스 생성
  • package.json 의 script를 보면 "test:e2e"가 있음. src 폴더 내의 파일 실행 X
  • 서버를 끄고 터미널에서 npm run test:e2e를 입력해보자.

새로운 E2E 테스트 만들기

test 폴더에 auth.e2e-spec.ts 파일을 만들고 app.e2e-spec.ts의 내용을 복사 붙여넣자.

이 후 조금의 수정을 가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// auth.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('Authentication System', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  it('handles a signup request', () => { // 알아보기 쉬운 주석
    return request(app.getHttpServer())
      .post('/auth/signup') // signup 경로
      .send({ email: 'asgkege@akl.com', password: 'qownstn' }) // 무작위 body
      .expect(201)
      .then((res) => { // response id, email로 올 것
        const { id, email } = res.body;
        expect(id).toBeDefined();
        expect(email).toEqual(email);
      });
  });
});

터미널에서 npm run test:e2e를 실행하면 문제가 발생한다. 이유를 알아보자.

테스트 오류

테스트 오류 내용을 보면 status 201 created를 예상했지만 500 Internal Server Error 이 발생하였음을 알 수 있다.

좀더 살펴보면 “Cannot set property ‘userId’ of undefined” 라는 문구를 볼 수 있다.

개발 중에는 애플리케이션이 실행되면 각 모듈(Users Module, Reports Module)이 실행되어 App Module이 실행되고 이는 main.ts 파일에서 import 된다. main.ts 내의 bootstrap function이 실행되면 그 이후로는 port:3000을 listening하는 것이 애플리케이션을 실행하는 것이다. 따라서 개발 중에는 main.ts에 의해 애플리케이션이 실행된다. 그리고 main.ts 내의 cookie-session이나 Validation pipe도 실행되는 것이다.

하지만 test중에는 main.ts는 관여되지 않고 E2E 테스트가 App module을 import하여 실행한다. 따라서 cookie-session이나 Validation pipe도 실행되지 않는다. 따라서 session이 없기 때문에 undefined로 처리되어 위 에러 문구가 발생한다.

해결책

새로운 setup-app.ts 파일을 만들어 미들웨어 과정을 호출하는 함수를 정의하는 방법도 있지만 좀더 nest다운 방법은 따로 있다.

현재 main.ts에서 전역으로 설정되어 있는 Validation Pipe와 cookie-session을 App Module에서 정의하면 된다. 테스트 환경에서도 main.ts는 실행되지 않아도 App Module은 사용하기 때문이다. 이 방법을 위해 Validation pipe, cookie-session을 DI 해야한다.

Validation pipe 옮기기

app.module.ts 파일로 가져와보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// app.module.ts
import { Module, ValidationPipe } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      database: 'db.sqlite',
      entities: [User, Report],
      synchronize: true,
    }),
    UsersModule,
    ReportsModule,
  ],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_PIPE, // 추가된 코드
      useValue: new ValidationPipe({
        whitelist: true,
      }),
    },
  ],
})
export class AppModule {}

이제 모든 request때 Validationpipe가 실행될 것이다. main.ts에 남은 app.useGlobalPipes는 지워주자.

cookieSession도 가져와서 맨 아래 APpModule에 추가해주자. r기존 Main.ts에선 삭제한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//app.module.ts
import { Module, ValidationPipe, MiddlewareConsumer } from '@nestjs/common';
const cookieSession = require('cookie-session');
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(
      cookieSession({
        keys: ['anything'],
      }),
    )
      .forRoutes('*');
  }
}

1
2
3
4
5
6
7
8
9
10
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

이제 auth.e2e-spec.ts 파일로 가서 signup 관련 부분 email을 새로운 unique한 것으로 바꿔주고 npm run test:e2e를 실행해보

테스트 2개가 모두 통과하면 성공이다. 이미 등록한 email로 시도하면 오류가 발생한다. 즉 다시 테스트할 때마다 새로운 email 넣어줘야 한다.. 더 나은 테스트를 위해 각 테스트를 독립적으로 실행할 수 있도록 DB를 청소하거나 새로 만들도록 해야한다. 하지만 테스트한다고 DB 다 날리면 기존 서비스의 유저들은 매번 새로 가입해야한다. 따라서 테스트용, 개발용 을 나누는게 최선이다. 이를 위해 환경 변수(environment variables)를 다루긴 너무 어려우니 ConfigService를 이용해보자