Given-When-Then은 주로 BDD(Behavior-Driven Development)에서 사용되는 테스트 시나리오 작성 패턴으로 테스트 의도를 쉽게 공유하고 이해할 수 있게 해줍니다.

 

  • Given (상황 설정):
    • 테스트의 시작 상태 또는 전제 조건을 설정합니다.
    • 애플리케이션이 어떤 상태에 있는지를 설명합니다.
  • When (행동):
    • 테스트하려는 특정 행동이나 이벤트를 정의합니다.
    • 애플리케이션이 처리해야 할 동작을 설명합니다.
  • Then (결과):
    • 행동이 발생한 후 기대하는 결과를 정의합니다.
    • 애플리케이션이 어떻게 반응해야 하는지를 설명합니다.
  • 예시
    • (Given) 사용자가 로그인하지 않은 상태에서 시작한다.
    • (When) 사용자가 로그인 버튼을 클릭한다.
    • (Then) 사용자가 대시보드 페이지로 이동한다.

e2e 테스트 예시

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('AuthController (e2e)', () => {
  let app: INestApplication;

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

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

  afterAll(async () => {
    await app.close();
  });

  it('등록되지 않은 사용자가, 로그인을 요청을 시도한다. 그러면 401 Unauthorized 상태 코드를 받아야 한다', async () => {
    // Given: 사용자가 등록되지 않은 상태
    const loginPayload = {
      email: 'nonexistent@example.com',
      password: 'invalidpassword',
    };

    // When: 사용자가 로그인 요청을 시도한다
    const response = await request(app.getHttpServer())
      .post('/auth/login')
      .send(loginPayload);

    // Then: 401 Unauthorized 상태 코드를 받아야 한다
    expect(response.status).toBe(401);
  });
});

 

 

라이브러리 설치

- Nest.js 를 이용하여 프로젝트를 생성하면 기본적으로 설치되어 있는 라이브러리

$ npm init
$ npm i -D jest ts-jest @jest/globals @types/jest

 

- API 테스트를 위해 설치해야하는 라이브러리

$ npm i supertest

 

 

테스트 파일의 형식

  • ___.test.ts
  • ___.spec.ts

실행 방법 ( pacakge.json 참고)

$ npm run test

 

콘솔 내용

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.527 s, estimated 2 s

Ran all test suites.

Jest did not exit one second after the test run has completed.

"This usually means that there are asynchronous operations that weren't stopped in your tests. 
Consider running Jest with `--detectOpenHandles` to troubleshoot this issue."

 

테스트를 한 후에 뭔가를 멈추지 않았다고 한다. 

 

콘솔에서 알려준대로 package.json 에서 "test:e2e" 에  --detectOpenHandles 추가

그 결과 해당 문구는 뜨지 않았다.

하지만 테스트가 끝나지 않아 내가 강제로 종료를 해야하는 상황이 발생.

 

이유는 Jest 공식문서: -- detectOpenHandles 를 참고해보니

이 옵션을 사용하면 성능이 저하되며 디버깅을 할때만 사용하라고 한다.

 

옵션을 추가하지 않고 해결하기 위해 다시 검색을  했다.

How To Fix: Jest Did Not Exit 에 따르면

나의 경우에는 app을 이용하여 연결을 하였지만 app 것을 끝내지 않아서 발생한 것 같다.

 

아래의 코드를 마지막에 추가하고 실행해보니 테스트가 끝나후 안전하게 종료 되었다.

afterAll(async () => {
    await app.close();
  });

 

 

e2e 테스트 중에 연결된 것 ( app, database) 은 테스트가 끝나면 모두 종료하자.

DI (Dependency Injection) : 의존성 주입 

  • 객체 지향 프로그래밍에서 중요한 디자인 패턴 중 하나
  • 객체가 필요로 하는 의존 객체를 외부에서 주입하하여 객체 간의 결합도를 낮추는 방식

 

사용하는 이유

의존성 주입을 통해 객체는 자신이 사용할 의존 객체를 직접 생성하지 않고, 외부에서 생성된 객체를 주입받기 때문입니다.

이로 인해 객체는 자신의 구현에만 집중할 수 있으며, 변경에 유연하게 대응할 수 있습니다.

 

장점

코드의 재사용성 증가, 코드의 유지보수성 향상, 객체 간의 결합도 감소, 단위 테스트 용이성 증가

 

 

 

Nest.js 에서 DI 하는 방법

  • DI는 의존성을 “런타임”시점에 주입시켜주는 기술
  • TypeScript는 컴파일 시점에만 인터페이스가 존재하고 “런타임” 시점에는 인터페이스가 사라지게 된다

→ DI컨테이너가 런타임 시점에 의존성을 주입시켜주지 못 해서 에러발생

 

프로바이더를 모듈에 등록할때 사용자 지정 프로바이더 문자열 토큰 주입 방식으로 해결

 

export interface TestRepository {
   outputText(text: string): void;
}
@Injectable()
export class TestConsoleLog implements TestRpository {
  outputText(text: string): void {
    console.log(text);
  }
}
@Module({
  imports: [TypeOrmModule.forFeature([Test])],
  controllers: [WorkoutLogController],
  providers: [TestService, 
         // provide에 문자열 토큰 지정
         { provide: 'test', useClass: TestConsoleLog }
         ],
})

    

@Injectable()
export class TestService {
  constructor(
    @Inject('test') //// Inject 데코레이터 사용해서 porivde에 등록한 문자열 토큰 사용
    readonly testRepository: TestRepository,
  ) {}
  
  outputMessage(message: string): void{
    this.testRepository.printText(message);
  }
  
}

 

문자열 토큰방식으로 프로바이더를 등록 후 의존하고 있는 인터페이스에 @Inject('등록한 문자열 토큰') 데코레이터를 달아주면 useClass에 명시한 구현체가 주입된다.

 

 

참고 자료

f-lab -스프링 프레임워크와 의존성 주입(DI) 이해하기

median - NestJS 인터페이스로 DI하기

실험 환경

Nest.js, TypeORM , MySQL, TypeScript,

 

코드

// soft delete
async softDeleteUser(users: User[]) {
    const userIds = users.map((user) => user.id);
    const startTime = Date.now();
    await this.userRepository.softDelete({ id: In(userIds) });
    const duration = Date.now() - startTime;
    this.logger.log(
      `(softDelete) Bulk update of ${userIds.length} users completed in ${duration}ms`,
    );
  }

// createQueryBuilder
  async softDeleteUserCreateQuery(users: User[]) {
    const userIds = users.map((user) => user.id);
    const startTime = Date.now();
    await this.userRepository
      .createQueryBuilder()
      .softDelete()
      .where('id IN (:...userIds)', { userIds })
      .execute();
    const duration = Date.now() - startTime;
    this.logger.log(
      `(createQueryBuilder) Bulk update of ${userIds.length} users completed in ${duration}ms`,
    );
  }

 

Postman 으로 실험해본 결과

 

이용 방식 1차 속도  2차 속도
softDelete 39 37
createQuery 37 39

 

결론

두 방식다 비슷한 속도를 보인다. 상황에 맞게 softDelete 와 createQuery 를 선택해서 사용하면 되겠다.

+ Recent posts