Jest를 이용한 Express 테스트

2022.01.02
8분
댓글


개발이 어느정도 진행되고, API 서버가 정상 작동하는지 포스트맨으로 일일히 확인해봤다.


하지만 MVP 단계이긴 하지만 규모가 점차 커질수록 기능이 많아져 수작업으로 테스트를
하는게 불안해지기 시작했다. 그래서 Jest를 이용한 단위 테스트로 TDD를 적용해 보고자
포스팅을 하게 되었다.


👠 Jest

Jest는 유닛 테스트, 통합 테스트, 함수 mocking 등 테스트 코드를 편히 작성할 수 있게 도와주는 프레임워크다.


❓ Jest를 사용하는 이유

lint가 코드 스타일에 규칙을 정하는 것이라면 코드가 올바른 기능을 하는 지 체크 할 수 있다.
이를 통해 보다 안정적이고 제대로 동작하는 코드를 작성할 수 있다.


🛠 Jest 설정하기

현재 개발 환경이 TypeScript이므로 ts-jest, @types/~ 관련 모듈도 설치해 준다.
그리고 supertestrequest통신을 만들어 주는 역할을 하니 express 테스트를 위해 필요하므로 같이 설치해 준다.

npm insatll --save-dev @types/jest @types/supertest jest ts-jest supertest
또는
yarn add --dev @types/jest @types/supertest jest ts-jest supertest

그 뒤 script 설정과 jest 설정을 따로 해줘야 한다.


package.json

{
    "scripts": {
        "test": "cross-env NODE_ENV=test jest --setupFiles dotenv/config --config jest.config.js --detectOpenHandles --forceExit"
    },
}

test 환경으로 핸들링 하기 위해 NODE_ENV를 test로 설정 한다.
env파일로 환경변수를 관리한다면 --setupFiles dotenv/config 꼭 넣어야 jest에서 환경변수를 사용할 수 있다.
Node.js에서 실행이 모두 완료 되어도 비동기가 남아있는 경우 jest가 종료되지 않는데 detectOpenHandles 옵션으로 열려있는 핸들링 을 모두 수집하고 forceExit 옵션으로 테스트 후 종료시켜 준다.


jest.config.js

module.exports = {
    preset: "ts-jest",
    moduleNameMapper: {
        "@routes/(.*)$": "<rootDir>/src/routes/$1",
        "@controllers/(.*)$": "<rootDir>/src/controllers/$1",
        "@services/(.*)$": "<rootDir>/src/services/$1",
        "@repository/(.*)$": "<rootDir>/src/repository/$1",
        "@entities/(.*)$": "<rootDir>/src/entities/$1",
        "@modules/(.*)$": "<rootDir>/src/modules/$1",
    },
};

TypeScript 환경에서 jest를 사용한다면 preset에 ts-jest를 명시해줘야 한다.
moduleNameMapper는 타입스크립트 환경에서 Path Alias를 사용하는 경우 꼭 설정해 줘야 테스트 할 때 경로를 알아서 찾는다.


이로써 Jest 기본 설정은 끝이다.


🛫 실제 테스트

프로젝트 구조

├── src             # 소스 폴더
    ├── modules         # 사용자 지정 모듈
    ├── routes          # 요청에 따른 분리
    ├── controllers     # API 요청/응답 실행
    ├── services        # 데이터 가공
    ├── repository      # DB CRUD
    ├── entities        # Model과 동일
    └── test            # Jest 테스팅 폴더, 이곳에 테스트 파일들이 모두 담긴다.

테스트를 하기위해 테스트 파일을 작성해야 하는데 아무 경로에 원하는이름.test.ts 이렇게 명시해주면 jest가 알아서 찾아 테스팅을 하게 된다.

  • ex) 유저 테스트를 하는 경우 user.test.ts 또는 user.spec.ts

🎓 Jest 기본 규칙

describe : 여러개의 테스트 케이스를 묶어 가독성을 높인다.
test/it : 테스트 단위

Methods LIST : 전역에 쓰면 모든 테스트가 적용되고 묶은 describe 속에 넣으면 그 속에 있는 test에만 methods가 적용된다.

  • afterAll(fn, timeout) : 모든 테스트가 끝나고 실행된다
  • afterEach(fn, timeout) : 하나의 테스트가 끝날 때마다 실행된다
  • beforeAll(fn, timeout) : 모든 테스트가 시작하기 전에 한번 실행된다.
  • beforeEach(fn, timeout) : 하나의 테스트가 시작하기 전에 매번 실행한다

예시

describe('테스트 단위', () => {
    beforeAll(() => {
        /* 모든 test() 함수 실행 이전에 작동한다. */
    });

    afterAll(() => {
        /* test() 함수가 모두 실행된 이후에 작동한다. */
    });

    test('무슨 테스트인지 여기에 작성한다.', () => {
        ...
    });
});
  • describe 단위로 여러 테스트를 진행하면 관리하기 용이하다.

🎬 Jest를 이용한 테스트

app.ts

if (env.nodeEnv !== "test") {
    createConnection()
        .then(() => console.log("🚀 DB Connected"))
        .catch((err) => console.log(err));
}

app.ts에서 DB 연결을 시도하면 테스팅이 끝날때 까지 Typeorm connection 이 끝나지 않아서 오류가 발생한다.
app.ts에 비동기 방식으로 짜여진 핵심 코드가 더 있다면 오류 날 확률이 생길 것이다. 그래서 NODE_ENV !== "test" 인 경우만 app.ts에서 DB 연결을 시도하고 테스팅 환경에선 테스트 파일에서 따로 DB 연결을 시도해 준다.


import-error.png

  • app.ts에서 DB 연결을 시도한 경우 import error가 뜬다.

src/test/shop.test.ts

import request from "supertest";
import { createConnection } from "typeorm";
import app from "../app";

describe("Api Test", () => {
    beforeAll(async () => {
        await createConnection()
            .then(() => console.log("🚀 DB Connected"))
            .catch((err) => console.log(err));
    });

    test("모든 가게 리스트 갖고 와야함", async () => {
        const response = await request(app).get("/v0/shops");

        // expect에 예상 값을, toBe에 결과 값을 넣는다.
        expect(response.status).toBe(200);
    });
}

위 코드는 Jest Tester가 /v0/shops URL에 GET요청을 보낸 후 예상 값과 결과 값을 비교 해 결과를 반환해준다.


result.png

현재 프로젝트에서 위 URL로 GET 요청을 보내면 DB에서 모든 가게 리스트를 조회 해, 성공 시 Http Status Code - 200 OK 응답을 보내준다.
위 코드에서 결과 값 200과 응답 값(예상 값)이 200으로 동일해 테스트 성공이라는 결과가 나왔다.


이런식으로 단위 테스트를 진행하면 되고 나중엔 통합 테스트, 부하 테스트를 공부한 뒤 포스팅 해야겠다.


📚 참고 자료