※ 이 글은 How to Use Extend with TypeSript 를 번역한 글입니다.

 

출처 - 구글 이미지 검색

mkdir src && cd src
mkdir __tests__
mkdir typings
mkdir example

 

Jest 는 현재 가장 인기 있는 테스팅 라이브러리 중 하나 입니다. 코드의 안정성과 신뢰성을 보장해주기 위해서 테스트 코드의 작성 여부는 선택이 아닌 필수가 되어버렸습니다. 

 

 

 

최근 1년간 NPM 다운로드 현황 (출처 - NPM 트렌드 현황)

 

 

기본적으로 Jest 는 여러 환경에서 테스트 코드를 작성할 수 있는 환경을 제공합니다. 물론 이 환경에서는 비동기 호출이나 다소 복잡했던 로직들도 테스트할 수 있는 환경 역시 구축할 수 있습니다. 그런데 Jest 가 제공하는 환경에서가 아니라 좀 더 구체적으로 내가 작업하고 있는 환경에 맞게 테스트하고 싶은 경우는 어떻게 해야할까요. 저의 경우는 "서버로 데이터를 요청하는 API 요청에 대한 응답값이 JSON 스키마와 항상 일치하는지 어떻게 확신할 수 있을까?" 에 대한 고민이 있었습니다. 왜냐하면 실제 프로덕션 레벨에서 다뤄지는 서버 데이터는 매 요청마다 결과 값이 다를 수 있기 때문에 상황이 복잡합니다. 주로 실시간 데이터인 항공 데이터나 숙박 검색과 같은 경우가 이런 경우입니다. 

 

 

이럴 때 Jestextend 는 커스터마이징 테스트 메소드를 구현할 수 있게 해줍니다.

 

 

 

 

프로젝트 구성하기

작업을 하기에 앞서 먼저 프로젝트를 구성을 시작하겠습니다.

npm init -y

 

 

필요한 패키지 역시 설치해 줍니다.

npm i -D jest ts-jest typescript @types/jest

// package.json
"scripts": {
  "test": "jest"
}

 

 

 

마지막으로, 폴더를 생성하면 끝입니다.

mkdir src && cd src
mkdir __tests__
mkdir typings
mkdir example

 

여기까지 따라왔다면, 디렉토리 구조는 아래와 같이 생성 됐을 것입니다. 간단해 보이지만 (실제로 간단하지만) 여기까지 잘 따라오셨다면 이제 모든 준비가 된 겁니다!

node_modules
src
|--- __tests__
|--- example
|--- typings
package-lock.json
package.json

 

 

 

 

 

설정 파일 작성하기

이 단계에서는, 타입스크립트가 우리가 테스트 코드를 작성할 때, 새롭게 만든 타입 함수들을 사용할 때 그게 뭔지 이해할 수 있도록 하는 사전 작업이 필요합니다. 별 다른 어려움이 없으니 천천히 따라해보세요.

 

 

먼저, 루트폴더에 jest.config.js 파일과 tsconfig.json 파일을 만드시기 바랍니다. 이 파일들은 테스트 코드를 실행할 때 파일 런타임 때 자동으로 읽히게 될 것입니다. 설정 파일을 따로 만들고 싶지 않다면 package.json 파일에 설정을 추가하는 식으로 대신할 수도 있습니다. 좀 더 많은 정보가 필요하다면 Jest 환경 설정을 클릭하세요.

 

touch jest.config.js
cd src && touch tsconfig.json

 

 

 

 

 

 

 

 

 

 

 

 

 

기본 테스트코드 작성

이제 기본적인 테스트 코드를 작성해 봅시다.

 

cd src && cd example
touch math.ts

 

 

 

 

 

그리고 방금 생성한 math.ts 파일을 테스트할 테스트 파일을 만듭니다.

테스트 파일을 만들 때 주의할 점은 반드시 테스트 파일 이름은 *.spec.* 형식으로 지어져야 합니다.

 

cd __tests__
touch math.spec.ts

 

 

 

 

 

mockedAdd 는 spy 함수로 spy 함수내에 파라미터로 전달된 원래 함수(math.add)가 사용됐을 때 추적을 할 수 있도록 하는 아주 유용한 기능의 메소드입니다. 원래 함수가 만약 객체라면 spy 함수는 그 객체의 주소 값을 복사한 값을 갖고 있는 새 변수라고 생각하는 것이 더 쉬울 수도 있겠습니다.

 

const initialObj = { sayHi: () => console.log('hi') };

// initialObj 의 주소 값을 가집니다.
// initialObj 의 변경점에 대해서 실시간으로 같이 공유받을 수 있다는 점을 기억하세요.
const spyObj = initialObj;

// spyObj 와 비슷한 개념으로(그러나 엄연히 다른 개념입니다) 기존 객체의 메소드의 변경 사항을 
// 계속 실시간으로 추적할 수 있다고 생각해보세요.
// 어떤 파라미터로 불렸는지, 몇 번 실행 됐는지 등 다양하게 사용처를 추적할 수 있습니다.
const spyFn = jest.spyOn(initialObj, 'sayHi');

spy 함수에 대해 더 자세한 정보를 보고 싶으시면 Jest 공식 문서를 참조하세요.

 

 

테스트 코드를 보면 expect() 를 통해 테스트하는 부분에서 서로 다른 방식으로 테스트를 하고 있는 부분을 보실 수 있을 겁니다. 위에서 짧게나마 소개한 spy 함수와 원래 함수의 반환 값을 이용해 테스트를 진행할 수도 있고 mock 객체에 포함된 calls, results 같은 원본 함수 추적용 배열을 사용해 테스트를 할 수도 있습니다.

 

 

그런데 테스트 코드를 작성하다보면 기본 테스트 메소드를 사용할 수 없는 경우가 있습니다. 예를 들어 값을 전달 받아 분기 처리를 한 후에 결정해야하는 경우 등이 그렇습니다. 그럴 때마다 그럼 테스트 코드를 이런식으로 작성해야 할까요?

 

if (num % 2 === 1) {
  expect(num).toBe(yourOdd);
} else {
  expect(num).not.toBe(yourOdd);
}

 

 

expect() 를 사용해 저런식으로 가정법과 같이 값에 따라 결과가 불확실한 형태로 테스트 코드를 작성하는 건 권장하고 싶지 않습니다. 테스트 코드 블록의 느낌을 산만하게 만들고 한 눈에 들어오기 점점 더 어려워지기 때문입니다.

 

 

이럴 때 사용하는 기능이 있습니다.

 

 

expect.extend()

Jest 는 테스터들이 좀 더 자유롭게 테스트 함수(이제부터는 matchers 라고 부르겠습니다) 를 입맛에 맞게 작성해서 사용할 수 있는 자율성을 갖고 있습니다. 타입스크립트에서 Jest 를 사용하는 경우, 타이핑 파일에 커스텀 matchers 에 대한 타이핑 정보가 없기 때문에 우리는 타입스크립트가 이해할 수 있도록 타이핑 파일을 먼저 작성하는 것부터 시작하겠습니다.

 

 

expect() 로 넘기는 값이 홀수인지 판별하는 커스텀 matcher 를 만들겠습니다.

 

 

cd src && cd typings
touch index.d.ts

 

 

 

 

 

 

여기까지 따라왔을 경우 폴더 구조는 다음과 같아야합니다.

 

node_modules
src
|--- __tests__
|    |
|    |--- math.spec.ts
|--- example
|    |
|    |--- math.ts
|--- typings
|    |
|    |--- index.d.ts
|
|--- tsconfig.json
jest.config.js
package-lock.json
package.json

 

typings 폴더를 만들고 그 안의 타이핑 파일에 toBeOdd() 에 대한 타입을 정의해주었지만 아직은 테스트 코드에 그 어떤 영향도 미치고 있지 않습니다. 타이핑 파일은 단순히 타입스크립트가 toBeOdd() 라는 메소드가 존재하고 어떤 파라미터를 받고 어떤 형태의 값을 반환해야하는지 알 수 있게해주는 파일입니다. 더 쉽게 말하자면 예를 들어서 window. 같이 window 를 입력하고 . 를 입력했을 때 내부에 포함된 메소드 목록들 중 하나로 나올 수 있게 타입스크립트한테 정보를 전달해주는 파일입니다. 

 

 

이제 테스트 코드로 돌아가서 실제 사용법을 보겠습니다.

 

 

 

 

 

 

 

 

 

 

 

우리가 만든 새로운 matchertoBeOddreceived 라는 파라미터를 제외하고 아무 파라미터도 받지 않는 함수입니다. received 는 테스트 코드에서 expect() 를 호출했을 때 그 안에 넘겨주는 값입니다. 즉 expect(10) 으로 시작했다면 received 의 값은 10 입니다. 위 코드에서는 received 는 각각 2 와 3 입니다. 

 

 

toBeOdd 메소드에 파라미터를 추가로 넘기고 싶다면 타이핑 파일부터 다시 수정해야하는데, 관련된 변경 사항들만 짚어보자면 다음과 같이 될 겁니다.

 

// index.d.ts
declare global {
  ...
  interface matchers<R> { 
    toBeOdd(a: number): R;
  }
}
// math.spec.ts
expect.extend({
  toBeOdd(received, a) {
    .. do something
  }
});
it('..', () => {
  /* received will be 3, and a will be 2 */
  expect(3).toBeOdd(2);
})

 

 

matchermessagepass 라는 값을 포함한 객체를 반드시 반환 값으로 반환해야 합니다.

 

 

message - 테스트가 실패 했을 경우 콘솔에 보여줄 오류 메시지를 반환하는 메소드입니다. 아무 인자도 받지 않습니다.

pass - boolean 타입의 값으로 어떤 형식으로 테스트가 불렸는지에 따라 각각의 message 함수를 리턴할 수 있게 해줍니다.

 

 

예를 들어서 pass 가 true 인 경우, message 함수는 matcherexpect(value).not.toBeOdd() 같은 형식으로 불려진 상황에서 테스트가 실패했을 때 실행됩니다. 반대로 pass 가 false 인 경우는 matcherexpect(value).toBeOdd() 의 형식으로 테스트가 실행된 상태에서 테스트가 실패했을 때 실행되게 됩니다.

 

 

/*
* pass: false
* 2 는 홀수가 아니므로 테스트는 실패합니다
* message 함수가 반환하는 메시지는 
* 'expected 2 not to be an odd number'
*/
expect(2).toBeOdd();
/*
* pass: true
* 1 은 홀수이므로 테스트는 실패합니다
* message 함수가 반환하는 메시지는 
* 'expected 1 to be an odd number'
*/
expect(1).not.toBeOdd();

 

 

 

최종 코드

 

npm run test math

 

 

 

 

 

결론

자바스크립트 테스팅 라이브러리는 프로젝트가 배포되기 전 코드의 품질을 보증할 수 있게 해줍니다. 그래도 테스트 코드를 작성하다보면 어느 테스팅 라이브러리를 사용하고 있든 상황에 맞는 필요한 matcher 를 찾기 힘든 상황이 오기 마련입니다. 그래서 이번 포스팅에서 소개해드린 기능은 여러분이 작성하고 있는 테스트 코드의 유연함을 더욱 증가시켜줄 것입니다.

+ Recent posts