티스토리 뷰

Github Repo

https://github.com/yhshin0/cardoc-subject

개요

  • 타이어 API 설계 및 구현

기간

  • 2021.11.22 - 2021.11.27

개발 환경

  • 언어 : TypeScript
  • 프레임워크 : NestJS
  • DB : SQLite3
  • 라이브러리 : axios, passport, jwt, bcrypt, typeOrm, class-validator
  • 배포 환경 : heroku

구현 사항

요구사항

  • RDB 사용
  • 실행 방법 서술
  • ORM 사용
  • response status(200 OK: 성공, 400 Bad Request: 잘못된 파라미터, 401 Unauthorized: 인증 헤더 오류, 500 Internal Server Error: 기타 서버 에러) 반환
  • 사용자 생성 API
    • ID / Password로 사용자 생성 구현
    • ID / Password로 로그인 구현
    • 인증 토큰 발급
    • 인증 토큰으로 인증된 사용자만 API 호출 가능
  • 사용자가 소유한 타이어 정보 저장 API
    • 자동차 차종 ID(trimID)를 사용하여 사용자가 소유한 자동차 정보 저장.
    • 타이어 정보 저장. 타이어 정보는 자동차 정보 API(trimID) -> spec -> driving -> frontTire/rearTire 에 있음
    • 한번에 최대 5명까지의 사용자에 대한 요청을 받을 수 있음. 즉, 사용자 정보와 trimID 5쌍을 요청데이터로 하여 API를 호출할 수 있음
      • 5명이 넘어가는 요청에 적당한 에러 처리
    • {폭}/{편평비}R{휠 사이즈}과 같은 형식의 데이터일 경우만 DB에 항목별로 나누어 서로 다른 Column에 저장
      예: 205/75R18 -> 타이어 폭: 205, 편평비: 75, 휠 사이즈: 18
    • 유효하지 않은 trimID 일 경우 에러 처리(유효하지 않은 trimID로 자동차 정보 API를 호출하면 {"code":-1000,"message":"No value present"} 라는 response를 받음)
  • 사용자 소유 타이어 정보 조회 API
    • 사용자 ID를 통해 저장한 타이어 정보를 조회할 수 있음
    • 페이지네이션
      • page, pageSize, totalCount(response) 사용

DB 모델링

AuthModule에서 env 값 사용하기

AuthModule에서 JwtModule을 사용하려면 register를 통해 등록해야 하는데, 단순 register() 만으로는 ConfigModule이 읽는 .env 파일의 값을 사용할 수 없습니다.
따라서 registerAsync()를 사용하여 .env 파일의 값을 직접 넣어줘야 합니다.
이를 위해서는 AuthModule에서 ConfigModule을 import 하고, registerAsync() 에서도 import 하여 ConfigService를 사용할 수 있도록 의존성을 주입해야 합니다.

 

auth.module.ts

@Module({
  imports: [
    UsersModule,
    PassportModule,
    ConfigModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get('JWT_SECRET'),
        signOptions: { expiresIn: configService.get('JWT_EXPIRATION') },
      }),
    }),
  ],

참고: https://velog.io/@algo2000/pj01-11
https://docs.nestjs.kr/techniques/configuration

private method mocking

private method는 mocking한 클래스에서 바로 사용하기 어렵습니다. 즉, jest.spyOn(tiresService, 'getTireInfoFromAPI') 과 같이 코드를 작성할 수 없습니다.
따라서 이를 해결하기 위해 mocking 하려는 메소드가 있는 클래스의 prototype에서 private 메소드가 실행되는지 확인해야 합니다.

 

tires.service.spec.ts

      ...

      jest
        .spyOn(TiresService.prototype as any, 'getTireInfoFromAPI')
        .mockResolvedValue(validAPIResult);

      ...

참고: https://stackoverflow.com/questions/56044471/testing-private-functions-in-typescript-with-jest

createQueryBuilder mocking

typeOrm의 createQueryBuilder를 통해 DB에서 데이터를 가져오는 경우 메소드 체이닝이 되어 있기 때문에 mocking 역시 그에 맞게 코드를 작성해야 합니다.
테스트 코드에서 객체를 작성하고 체이닝 된 메소드가 호출될 때 해당 객체가 반환될 수 있도록 객체를 정의합니다.
그리고 마지막 체이닝 메소드에 원하는 결과를 반환하도록 작성한 뒤, mockImplementation() 메소드를 통해 해당 결과가 나올 수 있도록 구현합니다.

 

tires.service.spec.ts

const mockTiresRepository = () => ({
  save: jest.fn(),
  createQueryBuilder: jest.fn(),
});
...
    it('유저 아이디를 통해 타이어 정보 조회에 성공한다', async () => {
      ...

      const createQueryBuilder = {
        innerJoin: () => createQueryBuilder,
        where: () => createQueryBuilder,
        skip: () => createQueryBuilder,
        take: () => createQueryBuilder,
        getManyAndCount: () => [tireList, tireList.length],
      };

      tiresRepository.createQueryBuilder.mockImplementation(
        () => createQueryBuilder,
      );

      const result = await tiresService.findByUserId(userId, page, pageSize);
      expect(result).toMatchObject({
        totalCount: tireList.length,
        data: tireList,
      });
    });

회고

Fact(사실)

  • 지난 프로젝트들과 달리 개인으로 진행하는 프로젝트였다.

Feeling(느낀점)

  • 개인 프로젝트라 나만 알아보는 코드가 되지 않을까 걱정된다.

Finding(교훈)

  • 최대한 알아보기 쉬운 코드를 작성하도록 해야겠다.

Future action(향후 계획)

  • 일주일 후 다시 코드를 리뷰하여 리팩토링한다.

Feedback(피드백)

-

Comments