Nest.js에서는 기본적으로 Jest를 이용한 Unit Testing과 e2e Testing을 지원한다.
기본으로 지원하는 기능을 통해 오늘부터 운동 기록 서비스의 TestCode 작성기 시작합니다 !!
🐳 첫 코드 - 실패
Nest.js의 공식 홈페이지와 Jest의 공식 홈페이지를 참고하여
가장 먼저 운동기록 테이블의 service 레이어를 테스트하는 코드를 작성하였다.
import { DataSource } from "typeorm";
import { RecordsRepository } from "./records.repository";
import { RecordsService } from "./records.service";
import { Record } from "./entity/records.entity";
describe('RecordsRepository', () => {
// 추후에 설명하겠지만 아래의 코드는 Nest.js의 특징을 살리지 못하는 코드이다 !
// createTestModule 생성을 통해 Nest.js의 강점을 살려 Testing 하자 !!
let recordsRepository: RecordsRepository;
let recordsService:RecordsService;
let datasource:DataSource;
beforeEach(async () => {
recordsRepository = new RecordsRepository(datasource);
recordsService = new RecordsService(recordsRepository)
})
})
먼저 테스팅에 사용할 recordsService 객체와 recordsRepository 객체를 선언해 주고
모든 테스트 전에 recordsService 객체와 recordsRepository 객체를 생성한다.
이때 의존성 주입을 ( ~~ ) 을 통해 해 준다!!
( 추후에 설명하겠지만 이는 Nest.js의 강점을 살리지 못한다. Nest.js의 강점을 살리기 위해서는 createTestingModule을 활용하자 )
그런 후에 테스팅할 코드를 작성해 준다.
가장 먼저 한 개의 운동 기록을 가지고 오는 getRecordById를 테스팅해보자!
describe('getRecordById', () => {
it('should return found record', async () => {
const id = 28;
const record = {
"id": 28,
"userId": 1,
"exercise": 1,
"setNum": 2,
"weights": [30,20],
"reps": [10,20],
"start_time": "2023-06-01 00:50:59",
"end_time": "2023-06-01 01:30:59",
"created_at": "2023-06-10 02:45:46",
"updated_at": "2023-06-10 02:45:46"
};
// recordService 에서 getRecordById 메소드를 호출하면
// recordRepository의 findOneBy를 호출하므로
// findOneBy를 spyOn 해주고
// 그 결과가 record 값이 나오도록 한다.
const findOneSpy = jest
.spyOn(recordsRepository, 'findOneBy')
.mockResolvedValue(record as Record);
const foundRecord = await recordsService.getRecordById(id);
expect(foundRecord).toEqual(record)
expect(findOneSpy).toHaveBeenCalledWith({id})
})
})
})
실행결과
읭 .. ?
TypeError: Cannot read properties of undefined (reading 'createEntityManager')
RecordRepository 에서 생성자로 datasource.createEntityManager() 가 실행되는데
이 부분에서 오류가 발생하는 것을 알 수 있다.
저 메소드가 정의되지 않았다고 한다.
datasource = new DataSource() ; 를 통해 datasource 객체도 생성해 주려고 했으나 이 부분에서 계속 오류가 발생했다.
테스팅 영역을 바꿔보자 !
🐳 Repository 영역을 Testing 해보자 !
결국 Service 레이어에서 Repository 영역을 호출하므로 Service 영역이 아닌 Repository 영역을 테스팅하자 !
import { RecordsRepository } from "./records.repository"
import { Test, TestingModule } from "@nestjs/testing";
import { DataSource } from "typeorm";
import { Record } from "./entity/records.entity";
describe('RecordsRepository', () => {
let recordsRepository: RecordsRepository;
// Repository 생성 과정에 보면 datasource.createEntityManager();를 통해 Repository를 만든다.
// 그러므로 repository 생성 과정에서 datasource가 없으면 오류가 발생한다 !!!
// 그렇기 때문에 datasource를 jest.fn()를 통해 Mocking 함수로 가로채주자 !
const datasource = {
createEntityManager:jest.fn()
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
RecordsRepository,
{
provide:DataSource,
useValue:datasource
}
]
}).compile();
recordsRepository = module.get<RecordsRepository>(RecordsRepository);
})
이번에는 beforeEach 부분을 변경해 주었다.
실제 repository를 생성하는 것이 아니라 Testing용 모듈을 생성해 보자 !
이 테스팅 모듈의 provider로는 RecordsRepository와 Datasource가 있는데 이 Datasource를 미리 만들어 둔 datasource로 사용해 제공하자.
const datasource = {
createEntityManager:jest.fn()
};
위의 코드는 repository 생성 시에 실행 되는 datasource.createEntityManager(); 를 Mocking하기 위해 선언한다.
Repository 생성 시에 datasource.createEntityManager()가 실행되면 실제 createEntityManager 대신 실행된다.
그 결과 우리가 원하는 값을 리턴하게 하거나, 함수를 재정의 할 수 있다.
이 후에 module을 compile 해주면 testing module인 "module"이 생성된다.
( console.log(module)을 해보니 굉장히 많은 정보들이 담겨 있는 객체인 것을 알 수 있었다. )
( 이해할 수는 없음... 진짜 짱 많음 ... )
그리고 생성한 module에서 recordsRepository 인스턴스를 .get<>()을 통해 가지고 오자 !
그리고 테스팅을 진행해 주자
describe('findOneBy', () => {
it('should return found record', async () => {
const id = 28;
const record = {
"id": 28,
"userId": 1,
"exercise": 1,
"setNum": 2,
"weights": [30,20],
"reps": [10,20],
"start_time": "2023-06-01 00:50:59",
"end_time": "2023-06-01 01:30:59",
"created_at": "2023-06-10 02:45:46",
"updated_at": "2023-06-10 02:45:46"
};
// repository의 method findOneBy가 실행되면
// 비동기결과값인 record를 return 한다.
const findOneSpy = jest
.spyOn(recordsRepository, 'findOneBy')
.mockResolvedValue(record as Record);
// 실제 메소드를 수행하고 그 결과를 foundRecord에 저장한다.
const foundRecord = await recordsRepository.findOneBy({id});
// 실제 값인 foundRecord와 우리가 예상한 결과 값인 record가 일치하는지 확인한다.
expect(foundRecord).toEqual(record)
// 그리고 우리가 입력한 id에 의해 호출 됐는지 확인한다.
expect(findOneSpy).toHaveBeenCalledWith({id})
})
})
})
실행 결과 !!
테스트 성공 !!!!
🐳 회고
repository 테스트 하나를 위해 정말 많은 시간을 소요했다 ...
먼저 테스트해야 할 부분을 잘 파악하는 것이 중요한 것 같고.
Jest의 동작 구조에 대해 더 이해할 필요가 있는 것 같다.
처음 테스트코드를 작성해 보았는데
기능 하나가 완성될 때마다 즉시 테스트 코드를 작성하는 습관을 길러야겠다.
그리고 이번에 테스트 코드를 작성해 보니
테스터블 한 코드를 작성하는 게 정말 쉬운 일이 아니구나 싶었다...
Nest.js를 더 이해하는 그날까지 화이팅 !
'Node.js > Nest.js' 카테고리의 다른 글
[Nest.js] Controller 라우팅 에러 (url 동적할당/ 정적할당) (0) | 2023.06.11 |
---|---|
[Nest.js] Nest.js의 동작 구조 (0) | 2023.06.11 |
[Nest.js] Entity와 DTO의 차이점 - [Error] unnamed portal parameter (0) | 2023.06.06 |
[Nest.js] ERROR [ExceptionsHandler] invalid input syntax for type integer (0) | 2023.06.03 |
[Nest.js] PartialType을 이용한 UpdateDto 만들기 (0) | 2023.06.01 |