Jest - TDD를 Jest로 직접 해보자 (6)
서론
저번 게시글에 이어서 날씨 정보를 받아오는 추가적인 기능을 만들어보자.
1단계 : 기능의 요구사항 및 스팩
저번 게시글에서 만든 기능은 지역코드, 시간, 날짜 를 보내면 그에 맞는 데이터를 반환하는 것이었다.
오늘은 시간과 날짜만 받아서 그것에 해당하는 전체 지역 데이터를 보내는 코드를 테스트를 이용해 만들어보자.
이 기능의 요구사항 및 스팩은 아래와 같다.
-
클라이언트로부터 시간과 날짜 데이터를 요청받으면 해당 시간과 날짜에 해당하는 전체지역의 데이터를 반환하는 'getAllWeatherInfo' 를 구현한다.
-
클라이언트로부터 받은 데이터는 {date:string, time:number} 의 형태이다.
-
반환하는 데이터형태는 [temperature:number, precipitation:number, precipitationPattern:number, windSpeed:number, windDirection:number, humidity:number] 와 같아야한다.
2단계 : 테스트 코드 작성
기능의 요구사항 및 스팩을 만족하는 테스트 코드를 작성하려면 아래와 같은 점을 고려해야한다.
-
클라이언트로부터 받을 데이터를 예제 데이터로 만든다.
-
'getAllWeatherInfo'가 반환하는 값을 예제 데이터로 만든다.
클라이언트로 부터 받은 데이터는 Dto를 이용해 옮길 것임으로 Dto를 추가로 만들어준다.
typescript1import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; 2 3export class SelectWeatherDto { 4 @IsNotEmpty() 5 @IsNumber() 6 area: number; 7 8 @IsNotEmpty() 9 @IsNumber() 10 time: number; 11 12 @IsNotEmpty() 13 @IsString() 14 date: string; 15} 16 17export class SelectAllWeatherDto { 18 @IsNotEmpty() 19 @IsNumber() 20 time: number; 21 22 @IsNotEmpty() 23 @IsString() 24 date: string; 25}
저번 게시글에서 사용했던 DTO도 추가로 작성해뒀으니 참고바란다.
우선 해당 DTO를 모듈로 Import 한다.
typescript1import { SelectWeatherDto, SelectAllWeatherDto } from './dto/weather.dto';
각자 DTO위치에 맞게 경로를 수정하자.
그리고 테스트 코드를 작성해보자.
모듈에 사용이 예상되는 weatherRepository의 메소드인 find를 추가해준다.
typescript1beforeEach(async () => { 2 const module: TestingModule = await Test.createTestingModule({ 3 providers: [ 4 WeatherService, 5 { 6 provide: getRepositoryToken(localEntity), 7 useValue: { 8 find: jest.fn(), 9 createQueryBuilder: jest.fn(), 10 }, 11 }, 12 { 13 provide: getRepositoryToken(weatherEntity), 14 useValue: { 15 findOne: jest.fn(), 16 find: jest.fn(), 17 }, 18 }, 19 ], 20 }).compile(); 21 22 service = module.get<WeatherService>(WeatherService); 23 localRepository = module.get<Repository<localEntity>>( 24 getRepositoryToken(localEntity), 25 ); 26 weatherRepository = module.get<Repository<weatherEntity>>( 27 getRepositoryToken(weatherEntity), 28 ); 29 });
정상적인 작동을 테스트하는 테스트코드를 작성해보겠다.
typescript1 describe('getWeatherAllInfo', () => { 2 it('should return weather information', async () => { 3 const clientData: SelectAllWeatherDto = { 4 date: '20230302', 5 time: 15, 6 }; 7 const returnWeatherInfo: weatherEntity[] = [ 8 { 9 id: 1, 10 area: 1, 11 time: clientData.time, 12 date: clientData.date, 13 temperature: 12, 14 precipitation: 0, 15 precipitationPattern: 0, 16 windSpeed: 0.2, 17 windDirection: 340, 18 humidity: 40, 19 }, 20 { 21 id: 2, 22 area: 2, 23 time: clientData.time, 24 date: clientData.date, 25 temperature: 15, 26 precipitation: 0, 27 precipitationPattern: 0, 28 windSpeed: 1.4, 29 windDirection: 150, 30 humidity: 35, 31 }, 32 { 33 id: 3, 34 area: 3, 35 time: clientData.time, 36 date: clientData.date, 37 temperature: 7, 38 precipitation: 50, 39 precipitationPattern: 3, 40 windSpeed: 2.3, 41 windDirection: 280, 42 humidity: 60, 43 } 44 ] 45 jest 46 .spyOn(weatherRepository, 'find') 47 .mockResolvedValue(returnWeatherInfo); 48 49 const weatherInfo = await service.getAllWeatherInfo(clientData); 50 51 expect(weatherInfo).toEqual(returnWeatherInfo); 52 expect(weatherRepository.find).toHaveBeenCalled(); 53 }); 54 });
우선 'selectAllWeatherDto' 형태의 Dto를 클라이언트로 요청받은 예시데이터를 작성한다.
그리고 반환할 예시 데이터를 weatherEntity[] 의 객체 배열 형태로 작성한다.
실제코드에서 사용이 예상되는 find 메소드를 mock 하고 'returnWeatherInfo' 가 반환되도록 설정했다.
그 후 'getAllWeatherInfo' 를 호출해 'clientData' 를 매개변수로 받고 반환되는 값이 'returnWeatherInfo' 와 같은지를 테스트한다.
또한 weatherRepository의 find 메소드가 제대로 호출되었는지 테스트한다.
이제 데이터베이스 오류를 테스트하는 코드를 작성해보자.
typescript1 it('should throw error when database error', async () => { 2 const clientData: SelectAllWeatherDto = { 3 date: '20230302', 4 time: 15, 5 }; 6 7 jest.spyOn(weatherRepository, 'find').mockRejectedValue(new Error('Database error')), 8 9 await expect(service.getAllWeatherInfo(clientData)).rejects.toThrow( 10 new Error('Database error'), 11 ); 12 });
똑같이 clientData 를 매개변수로 받는다.
그리고 find 메소드가 호출되었을때 'Database error' 라는 예외가 발생하도록한다.
'getAllWeatherInfo' 메소드를 호출하여 예외가 제대로 발생하고 err 코드를 제대로 반환했는지 테스트한다.
3단계 : 테스트 실패 확인
당연히 실제 코드가 없으니 테스트는 실패한다.
4단계 : 코드 작성
이제 테스트 코드를 바탕으로 실제 코드를 작성해보자.
typescript1 async getAllWeatherInfo( 2 selectWeatherDto: SelectAllWeatherDto, 3 ): Promise<weatherEntity[]> { 4 try { 5 return this.weatherRepository.find(selectWeatherDto); 6 } catch (err) { 7 throw err; 8 } 9 }
typeOrm의 find 메소드를 이용해 클라이언트로 부터 요청받은 데이터에 맞는 데이터를 찾아 반환하는 코드이다.
예외가 발생하면 에러코드를 throw 한다.
weatherEntity[] 형태의 객체 배열을 반환하고 매개변수로는 SelectAllWeatherDto 의 Dto를 받는다.
5단계 : 테스트 통과 확인
image
테스트를 통과했다.
6단계 : 리팩토링
리팩토링할 부분이 발견되지 않아 6단계는 패스한다.
7단계 : 2~6단계 반복
기능의 요구사항 및 스팩을 모두 만족했기때문에 7단계는 패스한다.