토이프로젝트/LinkArchive

Redux Provider로 감싸진 컴포넌트 테스트하기

여행 가고싶다 2023. 7. 15. 15:12

배경

 

다음과 같은 간단한 페이지를 테스트하려고 했다.

 

테스트 하고자 하는 페이지

 

_app.tsx

const App = ({ Component, ...rest }: AppPropsWithLayout) => {
  const { store, props } = wrapper.useWrappedStore(rest);

  const getLayout = Component.getLayout ?? ((page) => page);

  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <ThemeProvider theme={theme}>
          <GlobalStyle />
          <System />
          <MainLayout>
            <QueryClientProvider client={queryClient}>
              {getLayout(<Component {...props.pageProps} />)}
            </QueryClientProvider>
          </MainLayout>
        </ThemeProvider>
      </PersistGate>
    </Provider>
  );
};

export default App;

 

Login.tsx

const Login = () => {
  const dispatch = useAppDispatch();

  useEffect(() => {
    dispatch(routerSlice.actions.loadLoginPage());
  }, [dispatch]);

  return (
    <Wrapper>
      <h2>로그인 해주세요.</h2>
      <Link href={KakaoAuthUrl}>
        <Content>
          <Image src='/kakao_login.png' alt='kakao_login' fill />
        </Content>
      </Link>
    </Wrapper>
  );
};

 

Login.test.tsx

import '@testing-library/jest-dom';
import Login from './index';
import { render, screen } from '@testing-library/react';

test('로그인 페이지에서 "로그인 해주세요."라는 텍스트가 렌더링 되는지', () => {
  // ARRANGE
  render(<Login />);

  // ACT
  const heading = screen.getByText(/로그인 해주세요./);

  // ASSERT
  expect(heading).toBeInTheDocument();
});

 

에러 발생..

 

테스트 실패

 

팀 프로젝트의 로그인 페이지를 렌더링 하는 테스트를 작성하려 했는데, 'react-testing-library'의 'render' 함수를 이용하니 위와 같은 에러메시지가 나타났다.

 

이 에러는 Redux 스토어를 'Provider'로 감싸지 않고 컴포넌트를 렌더링 하려 했기 때문에 발생한 에러이다. 우리가 일반적으로 앱을 실행할 때는 'App' 컴포넌트를 'Provider'로 감싸서 Redux 스토어를 전달해 주지만, 테스트 환경에서는 이 과정을 생략하기 때문에 문제가 발생한 것이다.

 

물론 render()안에 Provider를 매번 넣어서 해결할 수 있겠지만 앞으로 수많은 테스트 코드를 작성해야 할 텐데 매번 Provider를 넣어주는 방식은 비효율적인 방식이라고 생각이 되어서 다른 방법을 찾아보기로 했다.

 

 

해결

 

이 문제를 해결하기 위해, 새로운 렌더링 함수를 만들어 'Provider'를 포함한 컴포넌트를 렌더링 하도록했다.

이 함수는 기본적으로  'react-testing-library'의 'render' 함수를 확장한 것으로, 추가로 Redux 스토어를 초기화하고 이를 'Provider'를 감싸는 역할을 한다.

 

import React, { PropsWithChildren } from 'react';
import { render } from '@testing-library/react';
import type { RenderOptions } from '@testing-library/react';
import { configureStore, Store } from '@reduxjs/toolkit';
import type { PreloadedState } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { RootState, persistedReducer } from '@/store';

interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
  preloadedState?: PreloadedState<RootState>;
  store?: Store;
}

export function renderWithProviders(
  ui: React.ReactElement,
  {
    preloadedState = {},
    store = configureStore({ reducer: persistedReducer, preloadedState }),
    ...renderOptions
  }: ExtendedRenderOptions = {}
) {
  const Wrapper = ({ children }: PropsWithChildren<{}>): JSX.Element => {
    return <Provider store={store}>{children}</Provider>;
  };

  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}

 

우리 앱에서는 redux-persist를 사용하고 있어서 

persistedReducer를 reducer로 전달하지만 그렇지 않는다면 일반 reducer를 넘겨주면 된다

.

store = configureStore({ reducer, preloadedState }),
    ...renderOptions

 

이제 render 대신에 renderWithProvider를 전달해주면 된다.

import '@testing-library/jest-dom';
import Login from './index';
import { screen } from '@testing-library/react';
import { renderWithProviders } from '@/utils/test-utils';

test('로그인 페이지에서 "로그인 해주세요."라는 텍스트가 렌더링 되는지', () => {
  // ARRANGE
  renderWithProviders(<Login />);

  // ACT
  const heading = screen.getByText(/로그인 해주세요./);

  // ASSERT
  expect(heading).toBeInTheDocument();
});

 

성공

 

 

 

 

 

 

 

 

 


 

 

 

참고한 사이트

 

Writing Tests | Redux

Usage > Writing Tests: recommended practices and setup for testing Redux apps

redux.js.org