티스토리 뷰
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(피드백)
-