@senspond

>

개발>프론트 엔드

NextJs 프로젝트에 Jest 테스트 프레임워크 적용하기

등록일시 : 2024-01-19 (금) 11:32
업데이트 : 2024-01-21 (일) 05:50
오늘 조회수 : 4
총 조회수 : 328

    Jest는 메타(구 페이스북)가 유지보수하는 재스민(jasmine) 위에서 빌드되는 테스팅 프레임워크입니다. 이 글은 NextJs 프로젝트에 Jset 프레임워크를 적용하는 방법을 정리해봅니다.

    Jest 테스트 프레임워크란?

    Jest는 메타(구 페이스북)가 유지보수하는 재스민(jasmine) 위에서 빌드되는 테스팅 프레임워크입니다. 바벨, 타입스크립트, Node.js, 리액트, 앵귤러, Vue.js, Svelte를 사용하는 프로젝트에서 정상적으로 동작되고 초기설정이 간단한 편입니다.


    자바 개발자라면 익숙한 Junit 테스트 프레임워크가 있는데요. 자바에 Junit이 있다면 자바스크립트에는 Jest가 있다고 볼 수 있겠습니다. 저는 이전에 Vue, React 환경에서 설정을 해봤던 적이 있었는데요. 현재 이 웹사이트의 프론트엔드는 NextJs 로 되어있는데, NextJs에서도 React에 적용하던 방식처럼 설정이 가능하였습니다. 그 내용을 정리해봅니다.


    Jest 프레임워크 세팅하기

    환경

    Jest 세팅하는 방법은 비슷하지만 환경에 따라서 약간의 차이가 있을 수 있어서 참고 용으로 기록합니다.


    • React18 / NextJs13 / TailwindCSS

    • TypeScript


    Jest 프레임워크 패키지 설치 & hello test

    npm i -D jest @types/jest 


    pageage.json

      "scripts": {
        "test": "jest"
      },


    그리고 [name].test.js 형식의 파일을 작성하면 됩니다.

    아래는 예시를 들기 위해 샘플코드를 만들어 보았습니다.


    hello.test.js

    const fs = require("fs");
    
    test("hello test", async ()=>{
        console.log("hello");
        const data = await  fs.readFileSync(__dirname + "/hello.test.ts", "utf-8")
        console.log(data)
    })


    npm run test 로 모든 테스트케이스를 수행할 수도 있고 아래와 같이 인텔리제이 같은 개발툴에서 해당 테스트 케이스에서 run 버튼을 클릭해 개별적으로 수행해줄 수 있다.



    테스트코드 파일의 위치를 어디에 둬야만 가능하다는 정해진 룰은 없지만 저는 테스트코드와 소스코드는 별개의 디렉토리에 두는 것을 선호하여 src 디렉토리에는 소스코드만 담고, __tests__ 라는 분리되어 있는 디렉토리를 만들어서 넣어줬습니다.



    Jest에 Require 대신에 import 가능하도록 설정



    React환경에서 ES Module Import 형태로 사용하기 위해서는

    npm i -D @babel/core @babel/preset-env @babel/preset-typescript

    babel.config.json 을 다음과 같은 설정해주게 되면 가능해집니다.

    {
      "presets": ["@babel/preset-env", "@babel/preset-typescript"]
    }



    오류가 사라지는 것을 확인 할 수 있습니다.


    그런데 NextJs에서는 서버구동시 건드린 바벨설정으로 인해서 오류가 발생할 수가 있는데요. NextJs에서는 커스텀 babel 설정을 삭제하고 jest.config.js 파일을 만들고 transform 아래처럼 넣어주어도 됩니다.

    module.exports = {
        transform: {
            "^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }],
        },
    };


    Jest에 외부모듈 alias 적용하여 import 가능하도록 설정

    jest 파일에서 외부모듈을 불러와 테스트를 할때


    import {AppService} from '../../../middleware/services/apps/AppService'


    와 같이 써야하는 것을

    import {AppService} from '@/middleware/services/apps/AppService'

    이런식으로 쓸 수 있게 만들어주는 설정이 필요한데요. 그 방법을 정리해봅니다.


    tsconfig.json

    "compilerOptions": { 
       "baseUrl": ".",
        "paths": {
          "@/*": [
            "./src/*"
          ],
    }

    TypeScript 를 사용중이라면 tsconfig.json에 이렇게 설정을 해주어야 합니다.


    Jest.config.js

    module.exports = {
        moduleNameMapper: {
            '@/(.*)$': '<rootDir>/src/$1',
        },
        transform: {
            "^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }],
        },
    };

    jest 설정파일을 만들어서 moduleNameMapper 에 경로를 지정해 정의하여 줍니다.


    pacakge.json

      "scripts": {
        "test": "jest --config jest.config.js"
      },

    test 커맨드로 jest 런타임 실행시 해당설정을 사용하도록 설정합니다.



    테스팅 라이브러리(jest-dom, react) 세팅

     npm i -D jest-environment-jsdom @testing-library/jest-dom @testing-library/react


    리액트 컴포넌트 테스트를 하려면 @testing-library/jset-dom @testing-library/react를 설치해줘야 합니다. @testing-library/react는 리액트 컴포넌트 testing을 위한 가상 돔(Virtual DOM)을 제공해주기 때문에 웹 브라우저 없이도 렌더링을 수 있고 elements와 상호작용 가능하며 테스트가 가능합니다.

    구버전에서는 모듈명이 react-testing-library 였지만 @testing-library/react 로 변경되었습니다.


    jest.setup.js

    import '@testing-library/jest-dom';

    jset.setup.js 파일은 Jest의 모든 테스트 코드에서 공통적으로 사용되는 설정이나 Mock 정의에 사용합니다.


    jest.config.js

    module.exports = {
        setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
        moduleNameMapper: {
            '@/(.*)$': '<rootDir>/src/$1',
        },
        testEnvironment: "jest-environment-jsdom",
        transform: {
            "^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }],
        },
    };

    위의 jest.config.js 보다 설정들이 추가 되었는데요. nextjs에서 react 컴포넌트를 jest를 통해 테스트를 하려면

    testEnvironment: "jest-environment-jsdom",

    transform: { "^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }],

    이런 설정들이 필요합니다.


    React 컴포넌트 Snapshot Testing

    스냅샷 테스팅(snapshot testing)이란 어떤 기능의 예상 결과를 미리 정확히 포착해두고 실제 결과에 비교하는 테스트 기법입니다. 테스트 대상 기능의 구현이 변경되어 실제 결과과 스냅샷을 떠놓은 예상 결과와 달라질 경우 해당 테스트 케이스는 실패하게 됩니다. 이런 경우, 다시 새로운 스냅샷을 떠서 기존 스냅샷을 교체하는 방식으로 테스트 코드와 함께 스냅샷도 함께 유지보수를 합니다. Jest와 같은 테스팅 라이브러리를 사용하면 이러한 스냅샷 테스팅을 위한 일련의 과정을 좀 더 편하게 수행할 수 있습니다.


    출처 : https://www.daleseo.com/jest-snapshot/



    아래와 같이 React 컴포넌트를 작성하고 가상 DOM으로 랜더링하여 스냅샷 테스팅을 하는 샘플코드를 작성했습니다.

    테스트 케이스를 수행하면,

    import React from "react";
    import {render} from "@testing-library/react";
    
    const TestComponent = (props : {message : string}) : React.ReactElement=>{
        return (
            <div>
                {props.message}
            </div>
        )
    }
    
    describe("snapshot test", ()=>{
        it("should render", ()=>{
            const component = render(<TestComponent message={"안내글입니다"} />)
            expect(component.container).toMatchSnapshot();
        });
    
    })

    __snapshot__ 이라는 디렉토리에 랜더링된 결과가 다음과 같이 생성된것을 확인 할 수 있습니다.



    그리고 snapshot 이 존재하는 상태에서 입력파라미터를 변경하고 다시 테스트를 수행하게 되면 TestCase는 실패되며 다음과 같이 출력됩니다.




    TailwindCSS 와 같이 사용할 때 발생이슈


    MessageDialog.test.tsx 를 작성하고

    import MessageDialog from "@/components/base/MessageDialog";
    import {render} from "@testing-library/react";
    
    describe("MessageDialog", ()=>{
    
        it("should render", ()=>{
            const component = render(<MessageDialog title = "안내" message="안내글입니다" isOpen={true}/>)
            expect(component.container).toMatchSnapshot();
        });
    })


    Tailwind CSS를 사용할 경우..

    Could not parse CSS stylesheet 라는 오류를 만날 수 있습니다.



    stackoverflow 에도 이 이슈에 대해서 언급된 내용이 있네요.

    소개되어 있는 방법은 아래와 같습니다.


    tailwindcss 를 컴파일해서 일반 css로 변환하고

    npx tailwindcss -i ./src/index.css -o ./src/test/index.css

    커스텀 Render 를 정의해서 그것을 사용하는 방식입니다.

    import { render, RenderOptions } from '@testing-library/react';
    import React, { FC, ReactElement } from 'react';
    import fs from 'fs';
    
    const wrapper: FC<{ children: React.ReactNode }> = ({ children }) => {
      return <>{children}<>;
    };
    
    const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => {
      const view = render(ui, { wrapper, ...options });
    
      const style = document.createElement('style');
      style.innerHTML = fs.readFileSync('src/test/index.css', 'utf8');
      document.head.appendChild(style);
    
      return view;
    };
    
    export * from '@testing-library/react';
    export { customRender as render };

    그런데 적용해보니 Next13 app route 방식을 사용하고 TailwindCSS 와 SCSS 를 같이 적용해 여러 파일로 나눠져있는

    현 프로젝트에서는 이대로 적용할 수 없었습니다.


    webpack 대신 Vite를 번들러로 사용하고 Vitest를 테스트 프레임워크로 사용하는 경우 좀더 간단하게 설정할 수 있다고 말하고 있는데, 추후에 신규 프로젝트에 그렇게 적용이 되는지 세팅을 한번 해보고 다시 정리해봐야 할것 같습니다.


    참고자료



    senspond

    안녕하세요. Red, Green, Blue 가 만나 새로운 세상을 만들어 나가겠다는 이상을 가진 개발자의 개인공간입니다.

    댓글 ( 0 )

    카테고리내 관련 게시글

    현재글에서 작성자가 발행한 같은 카테고리내 이전, 다음 글들을 보여줍니다

    @senspond

    >

    개발>프론트 엔드

    • 타입스크립트의 클로저, 클래스, 데코레이터로 싱글톤 패턴 구현하기

      타입스크립트의 클로저, 클래스, 데코레이터로 싱글톤 패턴 구현하기 정리를 해봤습니다.
        2024-05-22 (수) 07:59
      1. Fetch API 사용시 파일업로드 Multipart: Boundary not found 에러 해결 방법, 수동전송 방법(JAVA)

        Fetch API 사용시 파일업로드 Multipart: Boundary not found 에러 해결 방법을 정리하고 multipart/formdata 수동전송 방법을 정리한 내용입니다.
          2024-02-23 (금) 02:46
        1. [현재글] NextJs 프로젝트에 Jest 테스트 프레임워크 적용하기

          Jest는 메타(구 페이스북)가 유지보수하는 재스민(jasmine) 위에서 빌드되는 테스팅 프레임워크입니다. 이 글은 NextJs 프로젝트에 Jset 프레임워크를 적용하는 방법을 정리해봅니다.
            2024-01-19 (금) 11:32
          1. NextJs - Text content did not match. Server : "A" Client: "B" 오류 해결

            NextJs - Text content did not match. Server : "A" Client: "B" 오류 를 만나고 해결한 방법을 정리
              2023-12-07 (목) 12:23
            1. NextJs 에서 동적으로 Sitemap 만드는 방법

              이번 글에는 React 기반으로 만들어진 NextJs 프레임워크에서 동적으로 Sitemap 만드는 방법에 대해서 정리해보고 현재 발견한 문제점에 대해서 적어보려고 합니다.
                2024-01-03 (수) 05:33