본문 바로가기
SPA/React

[React] Jest에서 render 커스텀하여 전역 wrapper 설정하기

by J4J 2024. 1. 14.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 jest에서 render 커스텀하여 전역 wrapper 설정하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

Jest에서 발생될 수 있는 render 이슈

 

jest를 이용하여 개발한 컴포넌트를 테스트할 때 발생될 수 있는 간단한 이슈에 대해 먼저 얘기해 보겠습니다.

 

예를 들어 recoil을 사용하고 계시다면 index 또는 main 파일에 recoil 사용을 위한 <RecoilRoot> 설정을 다음과 같이 해야만 합니다.

 

// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RecoilRoot } from 'recoil';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(
    <React.StrictMode>
        {/* recoil 사용을 위한 RecoilRoot 설정 */}
        <RecoilRoot>
            <App />
        </RecoilRoot>
    </React.StrictMode>,
);

 

 

반응형

 

 

이후 해당 프로젝트에서는 jest를 이용하여 테스트 코드를 작성했을 때 recoil이 사용되는 컴포넌트인지 여부에 따라 RecoilRoot의 wrapper 설정이 다음과 같이 필요합니다.

 

import { render } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import App from './App';

describe('app test', () => {
    test('recoil test', async () => {
        render(<App />, {
            wrapper: RecoilRoot,
        });
        
        ...
    });
});

 

 

 

하지만 만약 RecoilRoot를 wrapper로 설정하지 않으면 테스트 코드는 올바르게 작성이 되었더라도 테스트가 정상적으로 동작되지 않게 됩니다.

 

그러다 보니 recoil 사용이 필요한 컴포넌트를 테스트할 때마다 매번 wrapper 등록을 해주셔야 합니다.

 

 

 

 

react로 개발할 때는 어떤 라이브러리를 가져와 사용하는지에 따라 recoil과 같이 전역 설정을 해줘야 하는 것들이 다양하게 존재합니다.

 

그리고 이런 설정들이 많아질수록 테스트 케이스에 설정해줘야 하는 wrapper 설정도 더 증가되고 편리하게 사용할 수 있는 방법이 무엇인지 고민하게 됩니다.

 

 

 

해당 이슈를 해결해 줄 수 있는 방법으로 필요한 wrapper들이 기본적으로 모두 등록된 커스텀 render를 작성하는 것이 있습니다.

 

사실 테스트 케이스의 개수가 많아질수록 필요로 하는 비용이 많아지기 때문에 실제 동작에 필요한 설정만 하는 것이 더 올바른 방법이라고 생각하긴 합니다.

 

하지만 매번 설정 값들이 등록된 커스텀 render를 사용을 해도 개발되는 프로젝트에 이슈를 발생시키지 않는다면 개발자 경험을 더 높일 수 있는 방법을 선택하는 것이 맞다고 판단합니다.

 

상황을 고려해보고 활용해보고 싶은 의향이 있으신 분들은 다음과 같이 적용해 보시면 됩니다.

 

 

 

 

커스텀 render 사용 방법

 

적용하기 전 간단한 예제 코드로 버튼을 클릭하여 값이 증가되는 경우에 대한 테스트를 작성해 보겠습니다.

 

// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RecoilRoot } from 'recoil';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(
    <React.StrictMode>
        {/* recoil 사용을 위한 RecoilRoot 설정 */}
        <RecoilRoot>
            <App />
        </RecoilRoot>
    </React.StrictMode>,
);


// src/use-app-store.ts
import { atom, useRecoilState } from 'recoil';

const useAppStore = () => {
    const [count, setCount] = useRecoilState<number>(
        atom<number>({
            key: 'count',
            default: 0,
        }),
    );

    return {
        count,
        increase: () => {
            setCount(count + 1);
        },
        decrease: () => {
            setCount(count - 1);
        },
    };
};

export default useAppStore;


// src/App.tsx
import useAppStore from './use-app-store';

export default function App() {
    const { count, increase, decrease } = useAppStore();

    return (
        <main>
            <div>
                <h2 role="count-text">Count : {count}</h2>
            </div>

            <div>
                <button role="increase-button" type="button" onClick={increase}>
                    increase
                </button>
                <button role="decrease-button" type="button" onClick={decrease}>
                    decrease
                </button>
            </div>
        </main>
    );
}


// src/App.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RecoilRoot } from 'recoil';
import App from './App';

describe('app test', () => {
    test('recoil test', async () => {
        render(<App />, {
            wrapper: RecoilRoot,
        });

        await userEvent.click(screen.getByRole('increase-button'));
        await userEvent.click(screen.getByRole('increase-button'));

        expect((await screen.findByRole('count-text')).textContent).toEqual('Count : 2');
    });
});

 

 

 

 

recoil이 동작되는 경우에 대한 테스트 작성이 필요하다면 일반적으로 위와 같이 작성될 것입니다.

 

여기서 커스텀 render를 새롭게 생성했을 경우 변경되는 코드는 다음과 같이 존재합니다.

 

// src/custom-render.tsx
import { RenderOptions, render } from '@testing-library/react';
import { ReactElement } from 'react';
import { RecoilRoot } from 'recoil';

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

export default customRender;


// src/App.test.tsx
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';
import customRender from './custom-render';

describe('app test', () => {
    test('recoil test', async () => {
        customRender(<App />); // customRender 사용으로 RecoilRoot wrapper 제거

        await userEvent.click(screen.getByRole('increase-button'));
        await userEvent.click(screen.getByRole('increase-button'));

        expect((await screen.findByRole('count-text')).textContent).toEqual('Count : 2');
    });
});

 

 

 

위와 같이 전역 wrapper를 활용하는 것으로 변경한다면 매번 wrapper 설정을 해줄 필요가 없어지기에 코드 작성이 간편해질 수 있습니다.

 

다만 위에서 얘기한 것처럼 필요하지 않은 경우에도 매번 모든 설정을 한다는 것을 참고하여 사용하면 좋을 것 같습니다.

 

 

 

 

 

 

 

 

이상으로 jest에서 render 커스텀하여 전역 wrapper 설정하는 방법에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글