Jest에서 class-validator 수행시 'Reflect.getMetadata is not a function' 오류

윤학·2023년 7월 6일
0

Jest

목록 보기
1/1
post-thumbnail

프로젝트에서 들어오는 요청의 데이터 유효성 검사를 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시켜야 위의 오류가 나지 않는다.

그럼 그 방법을 살펴보자.

1. setupFilesAfterEnv

jest에서는 테스트환경을 설정하고 초기화하는 과정을 특정 파일에 작성하고, setupFilesAfterEnv옵션에 해당 파일을 불러와 수행할 수있다.

필자는 reflect-metadata를 import하는 파일을 만들고 package.jsonjest구성 부분에서 해당 옵션을 지정해주었다.

jest.setup.ts

import 'reflect-metadata';

package.json

"jest": {
    "setupFilesAfterEnv": [
      "<rootDir>/jest.setup.ts"
    ]
  }

setupFilesAfterEnv 옵션에 지정한 파일들은 순서대로 실행이 되며, 각 파일의 import문보다 먼저 실행이 된다고 한다.

2. 각 파일의 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"

두개의 옵션중에 취향대로 쓰면 되는 것 같다.

참고

class-validator
ChatGPT

profile
해결한 문제는 그때 기록하자

0개의 댓글