본문 바로가기
SPA/React

[React] 타입 스크립트(TypeScript) 환경에서 Jest를 이용하여 테스트하기

by J4J 2022. 3. 24.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 타입 스크립트(TypeScript) 환경에서 Jest를 이용하여 테스트하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

반응형

 

 

테스트 (1) - JS 작성하여 테스트

 

가장 먼저 테스트 파일에 JS를 작성하여 테스트를 진행해보겠습니다.

 

 

 

[ 1. 패키지 설치 ]

 

$ npm install -D jest @types/jest

 

 

 

[ 2. package.json에 jest 추가 ]

 

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

 

 

 

[ 3. 테스트 파일 생성 ]

 

/src/test 경로를 생성한 뒤 테스트를 위한 js.test.ts 파일을 다음과 같이 생성하겠습니다.

 

js.test.ts 생성

 

 

 

그리고 테스트 코드를 다음과 같이 작성하겠습니다.

 

describe('js test', () => {
    it('number test', () => {
        expect(3+4).toBe(7); // 3+4가 7인지 테스트
    });

    it('string test', () => {
        const name = 'J4J';

        expect(name).toBe('J4J'); // name이 J4J인지 테스트
    });
})

 

 

 

[ 4. 코드 테스트 ]

 

코드를 작성했으면 jest를 이용하여 테스트를 하기 위해 다음 명령어를 입력해줍니다.

 

$ npm run test

 

 

 

이런 식으로 결과가 나오면 정상적으로 테스트가 수행된 것입니다.

 

테스트 성공

 

 

 

 

테스트 (2) - 모듈을 로드하여 테스트

 

테스트 파일들을 작성하는 이유는 여러 소스에서 사용되는 코드들이 우리가 생각하는 것처럼 동작되는지를 테스트하기 위해서입니다.

 

그러므로 대부분 여러 소스에서 작성해둔 모듈들을 로드하여 테스트를 하는데 대부분 import를 이용하여 모듈을 로드하실 겁니다.

 

하지만 지금 상태에서 import를 사용할 경우 코드를 올바르게 인식하지 못합니다.

 

보통 이런 상황에서 트랜스 파일러인 babel을 사용하고는 하는데 typescript는 babel의 역할을 대신해줄 수 있기 때문에 typescript를 이용하여 추가 설정을 해주겠습니다.

 

 

 

[ 1. 패키지 설치 ]

 

$  npm install -D ts-jest

 

 

 

[ 2. jest.config.js 생성 ]

 

$ npx ts-jest config:init

 

 

 

명령어가 정상적으로 사용되지 않을 경우에는 다음과 같이 root경로에 jest.config.js 파일을 생성해주면 됩니다.

 

jest.config.js 생성

 

 

 

내용은 다음과 같이 입력해주시면 됩니다.

 

/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

 

 

 

[ 3. 테스트 코드 작성 ]

 

/src 경로에 테스트를 할 코드들을 미리 작성해두겠습니다.

 

jsInfo.ts라는 이름으로 파일을 생성한 뒤 다음과 같이 내용들을 입력하겠습니다.

 

export const name = 'J4J';

export const getName = () => {
    return `제 이름은 ${name}입니다.`;
}

export const getSum = (param: number) => {
    const number1 = 3;
    const number2 = 4;

    return number1 + number2 + param;
}

 

 

 

[ 4. 테스트 파일 생성 ]

 

위에서 작성한 코드를 테스트하기 위해 이전과 동일하게 /src/test 경로에 jsInfo.test.ts 파일을 생성한 뒤 다음과 같이 내용을 입력하겠습니다.

 

import { name, getName, getSum } from '../jsInfo';

describe('jsInfo test', () => {
    it('name test', () => {
        expect(name).toBe('J4J'); // name이 J4J인지 테스트
    });

    it('getName test', () => {
        expect(getName()).toBe('제 이름은 J4J입니다.'); // getName()이 제 이름은 J4J입니다.인지 테스트
    });

    it('getName test', () => {
        expect(getSum(5)).toBe(12); // getSum(5)가 12인지 테스트
    });
})

 

 

 

[ 5. 코드 테스트 ]

 

이전과 동일하게 다음 명령어를 입력해주면 됩니다.

 

$ npm run test

 

 

 

정상적으로 테스트가 완료되었을 경우 이전에 작성했던 파일과 함께 테스트가 함께 진행되는 것을 확인할 수 있습니다.

 

테스트 성공

 

 

 

 

테스트 (3) - DOM 테스트

 

이번엔 DOM 테스트를 해보겠습니다.

 

간단하게 화면에 어떻게 보이고 있는지, 버튼을 눌렀을 때 우리가 생각하는 결과가 나오는지 등을 테스트해보겠습니다.

 

 

 

[ 1. 패키지 설치 ]

 

$ npm install -D @testing-library/jest-dom @testing-library/react

 

 

 

[ 2. setupTest.js 생성 ]

 

테스트 환경 설정을 위해 root 경로에 setupTest.js 파일을 생성해줍니다.

 

그리고 해당 파일에는 위에서 설치한 jest-dom을 로드해주기 위한 코드를 작성해줍니다.

 

require('@testing-library/jest-dom'); // jest-dom 모듈 로드

 

 

 

[ 3. tsconfig.jest.json 생성 ]

 

코드 사용환경과 별개로 Jest가 실행될 때만 추가 설정 변경을 위해 Jest 전용 tsconfig.json 파일을 root 경로에 tsconfig.jest.json 이름으로 생성해줍니다.

 

내용은 다음과 같이 입력해줍니다.

 

{
    "extends": "./tsconfig.json", // tsconfig.json 설정 적용
    "compilerOptions": {
        "jsx": "react" // jsx 설정 react로 적용
    }
}

 

 

 

[ 4. jest.config.js 수정 ]

 

지금까지 만든 파일들을 적용해주기 위해 위에서 만들었던 jest.config.js 파일을 다음과 같이 수정해줍니다.

 

/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
    preset: 'ts-jest',
    testEnvironment: 'jsdom', // jsdom으로 수정
    setupFilesAfterEnv: ['<rootDir>/setupTest.js'], // setupTest를 이용하여 환경 설정
    globals: {
        'ts-jest': {
            tsconfig: 'tsconfig.jest.json' // tsconfig.jest.json 사용 설정
        }
    }
};

 

 

 

 

[ 5. 테스트 코드 작성 ]

 

DOM 테스트를 위해 App.tsx 파일을 다음과 같이 수정하여 테스트하는데 사용해보겠습니다.

 

import * as React from 'react';

const App = (): JSX.Element => {

    const [number, setNumber] = React.useState<number>(0);

    return (
        <div>
            <h2>Hello. Jest!!</h2>
            <h4>number: {number}</h4>
            <button onClick={() => setNumber(number+3)}>증가</button>
            <button onClick={() => setNumber(number-2)}>감소</button>
        </div>
    )
}

export default App;

 

 

 

실행되는 화면은 다음과 같습니다.

 

실행 화면

 

 

 

[ 6. 테스트 파일 생성 ]

 

작성한 코드를 테스트하기 위해 /src/test 경로에 테스트 파일을 추가 생성해보겠습니다.

 

이번에는 react 코드를 테스트하기 때문에 ts파일이 아닌 tsx파일을 만들어야 하기 때문에 app.test.tsx 이름으로 생성한 뒤 다음과 같이 작성하겠습니다.

 

import * as React from 'react';
import App from '../App';
import { fireEvent, render } from '@testing-library/react';

describe('<App /> test', () => {
    it('matches snapshopt', () => {
        const utils = render(<App />);
        expect(utils.container).toMatchSnapshot(); // snapshot match
    });

    it('screen test', () => {
        const utils = render(<App />);

        const h2 = utils.container.querySelector('h2'); // h2태그 DOM 가져오기
        h2 && expect(h2.innerHTML).toBe('Hello. Jest!!'); // h2의 innerHTML이 Hello, Jest!! 인지 테스트
    });

    it('button test', () => {
        const utils = render(<App />);

        const str = utils.getByText('number: 0'); // 텍스트가 number: 0인 DOM 가져오기
        const increaseButton = utils.getByText('증가'); // 텍스트가 증가인 DOM 가져오기
        const decreaseButton = utils.getByText('감소'); // 텍스트가 감소인 DOM 가져오기

        fireEvent.click(increaseButton); // 증가버튼 클릭이벤트 실행
        fireEvent.click(increaseButton); // 증가버튼 클릭이벤트 실행
        expect(str).toHaveTextContent('number: 6'); // str의 텍스트가 number: 6인지 확인

        fireEvent.click(decreaseButton); // 감소버튼 클릭이벤트 실행
        fireEvent.click(decreaseButton); // 감소버튼 클릭이벤트 실행
        expect(str).toHaveTextContent('number: 2'); // str의 텍스트가 number: 2인지 확인
    });
})

 

 

 

[ 7. 코드 테스트 ]

 

이번에도 다음 명령어를 이용하여 테스트를 해줄 수 있습니다.

 

$ npm run test

 

 

 

지금까지 작성한 모든 테스트 파일이 함께 실행되며 성공적으로 수행될 경우 다음과 같은 결과를 확인할 수 있습니다.

 

테스트 성공

 

 

 

추가로 이번 테스트 코드에는 snapshot을 설정해놔서 다음과 같은 snap 파일을 확인할 수 있습니다.

 

snap 파일

 

 

 

snap 파일에서는 테스트하는 컴포넌트 코드를 스크린샷 찍은거라고 생각하시면 됩니다.

 

그러므로 snap 파일에서 화면에 보여주는 코드들을 확인할 수 있고 한번 만들어진 snap 파일은 다음 test를 진행할 때 새롭게 진행되는 test 컴포넌트와 이전에 진행된 test 컴포넌트를 비교해주는데 사용됩니다.

 

 

 

 

Render Wrapper 사용하기

 

여기서 말하는 Wrapper는 index.tsx에 사용되는 Wrapper를 의미합니다.

 

예를 들어, router를 사용할 때는 다음과 같이 BrowserRouter를 Wrapper로 사용합니다.

 

ReactDom.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>, 
    document.querySelector('#root')
);

 

 

 

비슷한 예시로는 recoil을 사용하고자 할 땐 다음과 같이 RecoilRoot를 Wrapper로 사용합니다.

 

ReactDom.render(
    <RecoilRoot>
        <App />
    </RecoilRoot>, 
    document.querySelector('#root')
);

 

 

 

Jest를 사용할 때 주의해야 할 점은 위와 같이 Wrapper가 필요한 컴포넌트를 테스트할 때는 render에 Wrapper를 작성해줘야 합니다.

 

그러므로 만약 위에서 테스트했던 App.tsx에 recoil을 사용했다면 app.test.tsx 파일은 다음과 같이 수정이 이루어져야 합니다.

 

import * as React from 'react';
import App from '../App';
import { fireEvent, render } from '@testing-library/react';
import { RecoilRoot } from 'recoil';

describe('<App /> test', () => {
    it('matches snapshopt', () => {
        const utils = render(
            <RecoilRoot>
                <App />
            </RecoilRoot>
        );
        expect(utils.container).toMatchSnapshot(); // snapshot match
    });

    it('screen test', () => {
        const utils = render(
            <RecoilRoot>
                <App />
            </RecoilRoot>
        );

        const h2 = utils.container.querySelector('h2'); // h2태그 DOM 가져오기
        h2 && expect(h2.innerHTML).toBe('Hello. Jest!!'); // h2의 innerHTML이 Hello, Jest!! 인지 테스트
    });

    it('button test', () => {
        const utils = render(
            <RecoilRoot>
                <App />
            </RecoilRoot>
        );

        const str = utils.getByText('number: 0'); // 텍스트가 number: 0인 DOM 가져오기
        const increaseButton = utils.getByText('증가'); // 텍스트가 증가인 DOM 가져오기
        const decreaseButton = utils.getByText('감소'); // 텍스트가 감소인 DOM 가져오기

        fireEvent.click(increaseButton); // 증가버튼 클릭이벤트 실행
        fireEvent.click(increaseButton); // 증가버튼 클릭이벤트 실행
        expect(str).toHaveTextContent('number: 6'); // str의 텍스트가 number: 6인지 확인

        fireEvent.click(decreaseButton); // 감소버튼 클릭이벤트 실행
        fireEvent.click(decreaseButton); // 감소버튼 클릭이벤트 실행
        expect(str).toHaveTextContent('number: 2'); // str의 텍스트가 number: 2인지 확인
    });
})

 

 

 

테스트하기 위해 작성되는 컴포넌트마다 위와 같이 Wrapper를 각각 작성해주는 것은 상당히 불편하고 단순 노동이며 또한 관리하기도 쉽지 않습니다.

 

이를 해결할 수 있는 방법은 render에 Wrapper를 정의해줘서 테스트할 때마다 Wrapper를 작성하지 않고 Wrapper가 이미 작성된 render를 가져다 사용하는 겁니다.

 

render를 재 정의하여 더 편리하게 사용될 수 있도록 변경해보겠습니다.

 

 

 

 

[ 1. render.tsx 파일 생성 ]

 

render.tsx 파일은 render를 재 정의하는 코드가 들어가는 파일입니다.

 

/src/test/utils 경로에 파일을 생성한 뒤 다음과 같이 작성하겠습니다.

 

import * as React from 'react';
import { ReactElement } from 'react';
import { render as reactRender } from '@testing-library/react';
import { RecoilRoot } from 'recoil';

const render = (ui: ReactElement, { ...options } = {}) =>
    reactRender(ui, {
        wrapper: ({ children }) => (
            <RecoilRoot>
                {children}
            </RecoilRoot>
        ),
        ...options
    });

export * from '@testing-library/react';
export { render };

 

 

 

[ 2. app.test.tsx 파일 수정 ]

 

기존 파일에서 render를 위에서 생성한 render로 가져오도록 수정하고 또한 컴포넌트마다 덮여있는 RecoilRoot를 모두 걷어내겠습니다.

 

import * as React from 'react';
import App from '../App';
import { fireEvent } from '@testing-library/react';
import { render } from './utils/render';

describe('<App /> test', () => {
    it('matches snapshopt', () => {
        const utils = render(<App />);
        expect(utils.container).toMatchSnapshot(); // snapshot match
    });

    it('screen test', () => {
        const utils = render(<App />);

        const h2 = utils.container.querySelector('h2'); // h2태그 DOM 가져오기
        h2 && expect(h2.innerHTML).toBe('Hello. Jest!!'); // h2의 innerHTML이 Hello, Jest!! 인지 테스트
    });

    it('button test', () => {
        const utils = render(<App />);

        const str = utils.getByText('number: 0'); // 텍스트가 number: 0인 DOM 가져오기
        const increaseButton = utils.getByText('증가'); // 텍스트가 증가인 DOM 가져오기
        const decreaseButton = utils.getByText('감소'); // 텍스트가 감소인 DOM 가져오기

        fireEvent.click(increaseButton); // 증가버튼 클릭이벤트 실행
        fireEvent.click(increaseButton); // 증가버튼 클릭이벤트 실행
        expect(str).toHaveTextContent('number: 6'); // str의 텍스트가 number: 6인지 확인

        fireEvent.click(decreaseButton); // 감소버튼 클릭이벤트 실행
        fireEvent.click(decreaseButton); // 감소버튼 클릭이벤트 실행
        expect(str).toHaveTextContent('number: 2'); // str의 텍스트가 number: 2인지 확인
    });
})

 

 

 

[ 3. 코드 테스트 ]

 

$ npm run test

 

 

 

 

테스트를 해보면 recoil을 사용하고 RecoilRoot를 추가해주지 않았음에도 다음과 같이 정상적으로 실행됩니다.

 

테스트 성공

 

 

 

RecoilRoot 뿐만 아니라 index.tsx에 등록되는 Wrapper들인 ThemeProvider, redux Provider 등 모두 사용이 가능하기 때문에 편리하게 테스트 환경을 구성해서 사용하시길 바랍니다.

 

 

 

 

 

 

 

이상으로 타입 스크립트(TypeScript) 환경에서 Jest를 이용하여 테스트하는 방법에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글