프로젝트에서 들어오는 요청의 데이터 유효성 검사를 class-validator를 이용하여 진행하고 있었다.
이미 프론트엔드 코드에서 유효성검사를 통해 Api요청 버튼이 눌리지 않게 해놓았지만, 직접 Api를 만들어 보낼 수 있기에 도입해보았다.
속성에 달아놓은 규칙대로 오류를 잘 뱉는지 확인하기 위해 class-validator에서 제공하는 validate
함수를 통해 테스트를 진행했다.
아래와 같이 흔히 알려진 휴대폰 정규표현식 규칙을 정하고 테스트해보자.
@Matches(phoneRegExp) // /^\d{3}\d{3,4}\d{4}$/
readonly id: string; // 아이디( 전화번호 )
it('잘못된 전화번호 양식이 결과에 포함되어야 한다.', async () => {
/* id필드에 잘못된 값 테스트 */
const wrongPhoneNumber = '010111111111'
const testForm = plainToInstance(CommonRegisterForm, CommonRegisterForm.of(wrongPhoneNumber, '테스트', 19980101, SEX.MALE, ROLE.PROTECTOR));
const [result] = await validate(testForm);
expect(result.property).toBe("id");
expect(result.value).toBe(wrongPhoneNumber);
});
간단하게 테스트코드를 살펴보면 validate
함수는 클래스를 기반으로 유효성검사를 실시하기에 plainToInstance
함수를 통해 데이터를 변환해주고, 형식이 잘못된 휴대폰번호가 validate
함수의 결과로 나오길 기대하며 테스트하고 있다.
근데 결과가...?
TypeError: Reflect.getMetadata is not a function
at node_modules/src/decorators/type.decorator.ts:15:44
at Object.<anonymous>.__decorate (src/user/interface/dto/register-page.ts:5:110)
at Object.<anonymous> (src/user/interface/dto/register-page.ts:195:5)
at Object.<anonymous> (test/unit/user/interface/dto/register-page.spec.ts:4:1)
Nest를 사용해봤기에 해당 문제가 reflect-metadata의 문제라고 생각했다.
하지만 왜 reflect-metadata패키지가 필요할까?
타입스크립트의 데코레이터는 런타임에 해당 요소에 대한 메타데이터를 설정하고 활용할 수 있게 해주는데,
이는 속성에 규칙(위에서는 휴대폰번호 규칙)을 정하면 class-validator가 reflect-metadata를 이용하여 다음과 같이 속성에 메타데이터를 설정한다고 한다.
Reflect.defineMetadata('design:type', String, User.prototype, 'name');
Reflect.defineMetadata('class-validator:metadata', [{ type: 'isNotEmpty' }], User.prototype, 'name');
그리고 validate
함수를 통해 유효성 검사를 수행할 때 reflect-metadata를 통해 설정한 메타데이터를 읽어온다고 한다.
그래서 테스트를 수행할 때는 reflect-metadata를 import시켜야 위의 오류가 나지 않는다.
그럼 그 방법을 살펴보자.
jest에서는 테스트환경을 설정하고 초기화하는 과정을 특정 파일에 작성하고, setupFilesAfterEnv옵션에 해당 파일을 불러와 수행할 수있다.
필자는 reflect-metadata를 import하는 파일을 만들고 package.json의 jest구성 부분에서 해당 옵션을 지정해주었다.
jest.setup.ts
import 'reflect-metadata';
package.json
"jest": {
"setupFilesAfterEnv": [
"<rootDir>/jest.setup.ts"
]
}
setupFilesAfterEnv 옵션에 지정한 파일들은 순서대로 실행이 되며, 각 파일의 import문보다 먼저 실행이 된다고 한다.
사실 Dto를 검사하는 테스트 파일에서만 필요한 패키지여서 필요한 파일에서만 import시키는게 나을거라 생각했다.
import { plainToInstance } from "class-transformer"
import { validate } from "class-validator"
import { ROLE, SEX } from "src/user-auth-common/domain/enum/user.enum"
import { CaregiverInfoForm, CaregiverLastRegisterDto, CaregiverThirdRegisterDto, CommonRegisterForm } from "src/user/interface/dto/register-page"
import { CaregiverRegisterDto } from "src/user/interface/dto/caregiver-register.dto"
import 'reflect-metadata';
하지만 처음에는 import를 시켜도 같은 오류가 났었는데 이유는 사용하려는 파일의 최상단에 import시켜야 하는데 그냥 최하단에 추가시키고 실행했기 때문이다.
reflect-metadata를 실제로는 CaregiverRegisterDto를 검사하는데 사용하고 있는데 타입스크립트의 데코레이터 기능을 이용하기 때문에 해당 파일 import문 이전에 import하면 정상적으로 실행은 되지만 안전하게 최상단에 위치시키는게 좋다고 한다.
import 'reflect-metadata';
import { plainToInstance } from "class-transformer"
import { validate } from "class-validator"
import { ROLE, SEX } from "src/user-auth-common/domain/enum/user.enum"
import { CaregiverInfoForm, CaregiverLastRegisterDto, CaregiverThirdRegisterDto, CommonRegisterForm } from "src/user/interface/dto/register-page"
import { CaregiverRegisterDto } from "src/user/interface/dto/caregiver-register.dto"
두개의 옵션중에 취향대로 쓰면 되는 것 같다.