nest.js tdd - unit test

0

nest.js에서도 테스트 주도 개발을 해보자.
node.js에서 jest로 테스트 주도 개발을 해보았다.

jest로 테스트 주도 개발하기

하지만 node.js에서 할때는 테스트 코드 작성을 어떻게 할지 몰라 api test만 진행했었다.
이번엔 service, controller를 각각 unit test 한다음, 최종적으로 api들을 E2E test할 예정이다.

라이브러리 설치

npm i -D @nestjs/testing

@nestjs/testing 패키지는 nest.js 테스트 코드를 작성할 때 유용한 다양한 유틸리티를 포함하고 있다. 


mocking하기

nest.js에서 test를 할때 편리한 점은, nest가 의존성 주입 기반으로 작동하기 때문에 mocking해주기가 쉽다는 것이다.

@Injectable()
export class AuthService {
  private slackClient: SlackApiClient;

  constructor(
    private jwtService: JwtService,
    private usersService: UsersService,
  ) {
    this.slackClient = new SlackApiClient();
  }
}

예를 들어 이건 AuthService의 의존성 주입 부분인데, 생성자에서 jwtService와 usersService를 의존성 주입해주고 서비스 함수들에서 jwtService와 usersService를 사용할 수 있게 된다.
이 의존성 주입 부분에 다른 class를 주입해도 정상적으로 돌아간다는 것이다.



class MockUserService {
	// userService mocking하기
}

class MockJwtService {
  // jwtService mocking하기 
}


 beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AuthService,
        {
          provide: JwtService,
          useClass: MockJwtService,
        },
        {
          provide: UsersService,
          useClass: MockUserService,
        },
      ],
    }).compile();

    authService = module.get<AuthService>(AuthService);
  });

따라서 test 부분에서 실제 UsersService를 사용하는 대신, UsersService를 mocking하여 만든 class를 대신 주입해주자.


 async signIn(data: SigninRequestDto): Promise<Tokens> {
    const { snsId } = data;

    const user = await this.usersService.findUser('snsId', snsId);

    if (!user) {
      throw new BadRequestException({
        message: '회원가입 되지 않은 유저입니다!',
        code: customErrorCode.USER_NOT_AUTHENTICATED,
      });
    }
    .
    .
    .
}

이렇게 AuthService의 회원가입코드에는 유저를 조회하는 UserService의 findUser함수가 사용되는데, 이 함수는 userService에서 테스트해주면 되기 때문이다. AuthService에는 AuthService의 함수들만 철저하게 테스트하고 그 하위 함수들은 해당 서비스들에서 철저하게 테스트 해주면 된다.


  createAccessToken(userId: number): string {
    return this.jwtService.sign(
      { userId: `${userId}` },
      {
        expiresIn: `${process.env.ACCESS_TOKEN_EXPIRE}`,
      },
    );
  }

그리고 이런 토큰 만드는 함수를 테스트할때, 예상값을 지정해 주어야하는데 토큰은 만들어지는 값을 예상할 수 없기 때문에 mocking 해주어야 한다. 그럼 토큰이 잘 만들어지는지 sign함수는 어떻게 테스트하나요? 라고 한다면 그건 다시 jwtService에서 테스트를 하면 된다.


class MockJwtService {
  sign() {
    return 'test_token';
  }
}

따라서 jwtService를 mocking하여 무조건 'test_token' 라는 값이 return되게 바꾸어 준다면 예상값을 제대로 예측할 수 있고


describe('createRefreshToken', () => {
  it('createRefreshToken success', async () => {
    const request = 1;
    const response = 'test_token';

    expect(authService.createRefreshToken(request)).toBe(response);
  });
});

이렇게 예상값을 확신할 수 있어 테스트가 용이해진다.


테스트 함수들

toBe

it('test', async () => {
  const request = 1;
  const response = 'test response';

  expect(authService.test(request)).toBe(response);
});

응답 값은 toBe를 통해 검증한다.


toStrictEqual

describe('test', () => {
  it('test', async () => {
    const request = 1;
    const response = { test: 'test response' };
    expect(authService.test(request)).toStrictEqual(response);
  });
});

응답 값이 객체라면 toStrictEqual를 통해 검사한다.


toUndefined

describe('test', () => {
  it('test', async () => {
    const request = 1;

    expect(authService.test(request)).toBeUndefined();
  });
});

return 값이 void인 함수들에 대해서는 toBeUndefined를 사용하면 된다.


resolves

describe('test', () => {
  it('test', async () => {
    const request = 1;
    const response = { test: 'test response' };

    expect(authService.test(request)).resolves.toStrictEqual(response);
  });
});

만약 비동기 함수에 대해 테스트 할때는 앞에 resolves를 붙혀주어야 한다.


rejects

describe('test', () => {
  it('test', async () => {
    const request = 1;
    const response =  new BadRequestException({
        message: '이미 가입된 유저입니다!',
        code: customErrorCode.USER_ALREADY_EXIST,
      });
    expect(authService.test(request)).rejects.toThrow(response);
  });
});

실패 테스트의 경우 rejects로 응답을 확인하면 된다.

profile
https://www.youtube.com/watch?v=__9qLP846JE

0개의 댓글

관련 채용 정보