Jest와 Express로 단위/통합 테스트하기

긴가민가·2024년 1월 4일
0
post-thumbnail

이전 글인 Typescript 절대 경로(alias) 적용하기를 기반으로 설명합니다.😊


요즘 리팩토링을 많이 하면서, 테스트의 중요성을 느끼고 있습니다.
TDD까진 아직 모르겠지만.. 테스트가 없으면 큰 규모의 시스템에서의 유지보수가 힘들거라 생각합니다.
그래서 Jest를 사용해서 단위/통합 테스트 환경을 만들어 보겠습니다!


Jest

설치

Jest 관련 모듈들을 설치하겠습니다.

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

설정

아래 명령어로 jest.config.ts와 초기 설정을 간단하게 진행합니다.

# jest.config.ts 초기 파일 생성하기
$ ./node_modules/.bin/jest --init

아래는 필자가 선택한 답변입니다.

Would you like to use Jest when running "test" script in "package.json"?

  • Yes

Would you like to use Typescript for the configuration file?

  • No

🚨 왜 TS를 사용하지 않지?

ts-jest configuration을 보면, js로 하고 preset 속성의 값을 "ts-jest"로 설정하라고 안내하고 있어요.

Choose the test environment that will be used for testing

  • node

Do you want Jest to add coverage reports?

  • Yes

Which provider should be used to instrument code for coverage?

  • v8

Automatically clear mock calls, instances, contexts and results before every test?

  • Yes

설치가 완료됐다면 jest.config.js 파일이 생깁니다.

파일 내부의 몇몇 속성을 변경하겠습니다.

// jest.config.js
{
  ...중략,
  preset: "ts-jest",
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1",
  },
  testMatch: ['**/__tests__/**/*.test.[jt]s?(x)'],
  ...중략,
}

moduleNameMapper : Alias가 설정된 모듈은 기본적으로 모킹 해제되기 때문에, Typescript에서 Path alias를 사용하는 경우 필수 설정
testMatch: 테스트를 할 경로 지정


테스트

이제 준비는 다 됐으니, 테스트를 해보겠습니다.
원활한 테스트를 위해 구조를 조금 바꿔보겠습니다.

일단 src 하위에 ___test___ 폴더와 routes 폴더를 만듭니다.
그리고 app.ts의 API route 부분을 모듈로 분리합니다.

변경 전

// app.ts
import express from "express";

const app = express(); // 서버 만들기

/**
 * @description 상태 체크하는 API
 */
app.get("/status", (req, res, next) => {
    console.log("Request status API success!!");

    res.send('GOOD!');
});

export default app;

변경 후

// app.ts
import express from 'express';
import { statusRoute } from '@/routes/status.route';

const app = express(); // 서버 만들기

/**
 * @description 상태 체크하는 API
 */
app.get('/status', statusRoute);

export default app;
// routes/status.route.ts
import { NextFunction, Request, Response } from 'express';

export const statusRoute = (req: Request, res: Response, next: NextFunction) => {
  console.log('Request status API success!!');

  res.send('GOOD!');
};

이 코드를 토대로 테스트를 진행해보겠습니다!
추가적으로, 테스트는 Given-When-Then 패턴으로 구성합니다.

💡 Given-When-Then 패턴?

Given: 테스트를 위해 필요한 Mock 또는 값을 설정하고 준비하는 단계
When: 실제 테스트 로직 호출 단계
Then: 테스트 검증 단계로써, 테스트 로직이 호출되면서 나타날 결과에 대해 예상 시나리오를 작성하고 검증

단위 테스트

단위 테스트는 하나의 모듈을 테스트하는 가장 작은 단위 테스트입니다.
모듈로 분리한 routes/status.route.ts를 테스트해보겠습니다.

// __tests__/status.route.test.ts
import { statusRoute } from '@/routes/status.route';
import { Request, Response } from 'express';

describe(`Status Route test :)`, () => {
  test(`Success :)`, () => {
    // given
    const req = {} as Request;
    const res = {
      send: jest.fn(),
    } as unknown as Response;
    const next = jest.fn();

    // when
    statusRoute(req, res, next);

    // then
    expect(res.send).toHaveBeenCalled();
    expect(res.send).toHaveBeenCalledWith('GOOD!');
  });
});

describe: 관련된 여러 테스트를 묶어서 관리할 수 있게 도와주는 함수
test: 실제 테스트 로직 (it과 같습니다.)
expect: 예상되는 결과와 실제 결과를 검증하는 함수
jest.fn(): 가짜(?) 함수를 만들어주는 함수

💡 혹시 'describe' is not defined. 에러가 발생한다면, .eslintrc.json 의 env 속성을 아래처럼 수정해주세요!

// .eslintrc.json
...중략
"env": {
    "browser": true,
    "es2021": true,
    "jest": true // 👈 이 부분!
  },
...중략

🚨 간략 설명

statusRoute() 함수에 들어가는 파라미터들을 먼저 정의합니다.
각 파라미터들은 타입이 지정되어있기 때문에, 선언한 변수들의 타입을 as를 사용하여 강제화합니다.
res의 경우, statusRoute 함수 내부에서 .send()함수를 호출하기 때문에 jest.fn()을 사용해 가짜 send 함수를 만듭니다.

이제 테스트 해보겠습니다.

성공!!

통합 테스트

통합 테스트는 모듈 간의 호환성을 확인하는 테스트입니다.
API Call 발생부터 종료까지의 과정을 테스트하는 것을 예로 들 수 있습니다.

설치

supertest 모듈을 통해 쉽게 테스트할 수 있습니다.

# supertest 모듈 설치
$ npm i -D supertest @types/supertest

테스트

// __tests__/status.api.test.ts
import app from '@/app';
import request from 'supertest';

describe(`Check status API test :)`, () => {
  test(`Success :)`, async () => {
    const { status, text } = await request(app).get('/status');

    expect(text).toEqual('GOOD!');
    expect(status).toBe(200);
  });
});

supertest를 사용해 GET /status 요청을 보내고, 그 응답을 예상 값과 검증하는 간단한 테스트입니다.

테스트를 해보면..

성공입니다!!

💡 테스트 방법에 대한 기술을 중점으로 작성했기 때문에 성공 테스트만 했지만, 실제 단위/통합 테스트는 실패 테스트가 훨씬 중요해요.
실패하는 경우에 대한 테스트도 작성해보는걸 추천드려요. :)


의견은 언제든 댓글로 남겨주세요.🙂

profile
미래의 내가 참고하려고 모아가는 중 :)

0개의 댓글