배경
다음과 같은 간단한 페이지를 테스트하려고 했다.
_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();
});
성공
참고한 사이트
'토이프로젝트 > LinkArchive' 카테고리의 다른 글
횡단 관심사 분리를 위한 Axios 인스턴스와 인터셉터 설정 (0) | 2023.07.27 |
---|---|
[문제 해결] Warning: An update to Profile inside a test was not wrapped in act(...). (1) | 2023.07.18 |
Next.js에서 토큰 재발급 구현하기 (3) | 2023.06.14 |