NestJS 11 | Unit Test - QueryBuilder & Transaction

임종성·2021년 9월 18일
9

NestJS

목록 보기
11/13
post-thumbnail

지난 블로그에서 NestJS가 지원하는 Test Framework인 Jest를 활용하여 Mocking 기반의 Unit Test를 작성했습니다. 이번엔 QueryBuilder와 Transaction을 활용하는 경우 Unit Test를 어떻게 작성하는지 살펴보도록 하겠습니다.

Repository의 의존성을 Mocking하여 외부환경에 영향을 받지 않는, 독립된 Test 환경을 조성했습니다. 그러나 다양한 기능과 복잡한 로직을 구현하다 보면 Repository외에 QueryBuilder, Connection 등 의존성을 주입받은 다양한 모듈과 라이브러리를 사용하게 됩니다. 이런 객체들의 Mocking을 어떻게 구현하는지 알아보겠습니다.

QueryBuilder Unit Test

지난번 getMfr Service Logic은 단순히 User Repository의 find method만 사용했습니다. 이번엔 getOrderQtyDetail Service를 작성하며 innerJoin, leftJoin이 여러번 사용되어 QueryBuilder를 활용했습니다.

Logic & Service

getOrderQtyDetail API가 사용되는 Layout은 다음과 같습니다.

Path Parameter로 OrderId를 받고, 그 요청에 대해 특정 Order의 Product에 대해서 Size 별 SKU, Barcode, 주문량과 선적수량, 도착수량과 그 합계들을 함께 반환해야 합니다. 그에 맞게 작성한 getOrderQtyDetail Service는 아래와 같습니다.

orderId에 대한 Order의 존재 유무 예외처리를 시작으로 QueryBuilder를 활용해 Order, Product, Barcde 등 Table을 모두 조인하여 필요한 결과만 뽑아냅니다. 그 후 뽑아낸 결과를 활용해 주문량, 선적수량, 도착수량 별 합계를 계산한 후 결과에 함께 반환합니다.

Unit Test with QueryBuilder

이제 코드가 실행되는 Service 환경과 같은 Test 환경을 만들어봅시다. 먼저 getOrerQtyDetail은 Order Service에 속하므로 Order Module과 같은 환경을 만들기 위해 Provider들을 Mocking해줘야 합니다.

Order Module에서 Import된 Repository들을 사용하기 위해 Testing Module에서도 Mocking된 Value를 사용하는 Repository를 공급합니다.

이제 QueryBuilder를 Mocking해주어야 합니다. QueryBuilder의 Method인 innerJoin, leftJoin, select 등의 특징은 바로 Chaining Function이라는 점입니다. 이런 Chain 특징을 Mocking하기 위해 mockReturnValue()mockReturnThis()를 활용합니다.

mockReturnThis()jest.fn(function () {return this;})의 Sugar Function으로, this를 반환하여 chained method를 mocking하는 것이 가능하도록 해줍니다.

이제 getOrderQtyDetail의 Test Code를 살펴보겠습니다.

Order의 예외처리에 대한 try, catch 사용은 지난번 Test와 동일합니다. 그 후 createQueryBuilder의 호출 여부를 확인하는 Test Case를 작성합니다. innerJoin은 3번, leftJoin은 2번 호출되었는지 toHaveBeenCalledTimes method로 확인합니다.

QueryBuilder의 method들이 호출되었음을 확인한 후, getRawMany의 반환값을 mocking한 후 getOrderQtyDetail의 반환값과 비교합니다. 최종 테스트 결과는 아래와 같습니다.

Transaction Unit Test

NestJS에서 Transaction은 QueryRunner와 Connection을 활용합니다. Transaction을 Test하기 위해 Connection 객체와 QueryRunner를 어떻게 mocking하는지 알아봅시다.

modOrderStatus

modOrderStatus는 Request로 받은 order들의 orderStatus를 변경해주는 기능입니다. 모든 update가 성공적으로 완료되어야 commit 하고, 도중에 실패한다면 모든 update를 rollback 하는 transaction을 적용했습니다. modOrderStatus 함수는 다음과 같습니다.

Unit Test with Transaction

마찬가지로 Test 환경을 modOrderStatus가 작성된 환경과 동일하게 만들어줍시다. orderRepository 이외에 추가적으로 Connection을 주입하여 사용하고 있기때문에 Connection 객체와 QueryRunner를 Mocking 해줍니다.

Repository, QueryBuilder를 Mocking했던 것과 마찬가지로 Jest Mocking Function을 활용해 QueryRunner의 각 method를 Test 환경에서 독립적으로 사용할 수 있도록 정의해줍니다. 이제 Test Case를 크게 3개로 나누어 작성해봅시다.

Exception Handling

먼저 OrderStatus를 수정하기 위해 요청받은 OrderId 리스트의 존재여부를 확인하고 예외처리 하는 과정에 대해 Test 합니다. 그 후 예외처리가 끝나고 QueryRunner가 호출되는 것을 확인합니다.

Transaction Fail

QueryRunner와 startTransaction이 실행되어 Transaction이 적용되었습니다. Transaction Test에서 중요하다고 생각하는 것은 Transaction이 실패했을 경우 rollback이 적용되는 것을 확인하고, 성공했을 경우 commit이 실행되는 것을 확인하는 것입니다.

Transaction이 실패했다고 가정하기 위해, spyOn 함수를 활용해 findOne method로 반환된 값이 undefined가 되도록 mocking합니다. 이제 수정하고자 하는 Order를 찾지 못하고 Error를 반환하여 Transaction이 실패할 것입니다.

Transaction Success

Transaction이 성공하면 update method와 commitTransaction을 호출해야 합니다. 마찬가지로 Transaction이 성공했다고 가정하기 위해 findOne method의 반환값이 존재하도록 mocking합니다.

Transaction이 성공하여 순차적으로 update, commitTransaction을 호출하고 isSuccesstrue를 반환하는 것을 확인합니다. 전체 TestCode와 결과는 다음과 같습니다.

이렇게 Test의 독립된 환경을 만들기 위해 QueryBuilder, QueryRunner, Connection을 Mocking하고, spyOnemockResolvedValue를 적절하게 활용하여 테스트하고자 하는 함수의 Logic의 분기점을 설정하여 TestCase를 작성할 수 있었습니다. 적절한 Parameter와 Return Value를 설정해준다면 Input과 Output에 대한 Test도 가능하지만, Logic에 중요하게 작용하는 함수나 Method의 호출 여부만을 Test했습니다.

profile
어디를 가든 마음을 다해 가자

0개의 댓글