November 02, 2022
먼저 테스트 코드의 많은 예제에서 다루는 것처럼 테스트의 기본은 컴포넌트가 제대로 render되는지
확인하는 것이라 생각하여 render시키고 난 후에 다음 것들을 생각해보고자 하였습니다. 분명히 마음처럼 쉽게 되지 않을 거라는 것을 알았기 때문입니다.
import Report from '../components/TabContent/Report';
import { render } from '@testing-library/react';
describe('Report', () => {
it('should render', () => {
render(<Report />);
});
});
바로 첫번째 이슈를 겪었습니다.
Jest useNavigate() may be used only in the context of a Router component
컴포넌트 내부에서 useNavigate()를 사용하고 있었는데 Router component로 감싸지 않아서 에러가 났습니다. Router component로 감싸서 해결했습니다.
import Report from '../components/TabContent/Report';
import { render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
describe('Report', () => {
it('should render', () => {
render(
<Router>
<Report />
</Router>,
);
});
});
이제는 react-redux 관련 에러가 났습니다.
could not find react-redux context value; please ensure the component is wrapped in a
<Provider>
관련 이슈를 검색해보니 redux를 test에서 사용할 때 가이드라인이 있어 해당 코드를 추가했습니다.
// src/utils/test-utils.tsx
import React, { PropsWithChildren } from 'react';
import { render } from '@testing-library/react';
import type { RenderOptions } from '@testing-library/react';
import { configureStore } from '@reduxjs/toolkit';
import type { PreloadedState } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import type { AppStore, RootState } from '@/store';
import { reducers } from '@/store';
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
preloadedState?: PreloadedState<RootState>;
store?: AppStore;
}
export function renderWithProviders(
ui: React.ReactElement,
{
preloadedState = {},
store = configureStore({ reducer: reducers, preloadedState }),
...renderOptions
}: ExtendedRenderOptions = {},
) {
function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
return <Provider store={store}>{children}</Provider>;
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}
import Report from '../components/TabContent/Report';
import { BrowserRouter as Router } from 'react-router-dom';
import { renderWithProviders } from '@/utils/test-utils'
describe('Report', () => {
it('should render', () => {
renderWithProviders(
<Router>
<Report />
</Router>
);
});
});
다시 돌려보니까 새로운 에러를 발견했습니다. 검색해보니 아래와 같은 솔루션을 발견할 수 있었습니다.
window.matchMedia is not a function
Jest test fails : TypeError: window.matchMedia is not a function Manual Mocks · Jest
import Report from '../components/TabContent/Report';
import { BrowserRouter as Router } from 'react-router-dom';
import { renderWithProviders } from '@/utils/test-utils'
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
describe('Report', () => {
it('should render', () => {
renderWithProviders(
<Router>
<Report />
</Router>
);
});
});
다시 돌려봤는데 다른 에러가 발생했습니다. 하지만 비슷한 원인의 에러인거 같아서 더 찾아봤고, window가 아닌 global 객체의 matchMedia를 mocking해야 하는 상황이었습니다.
import Report from '../components/TabContent/Report';
import { BrowserRouter as Router } from 'react-router-dom';
import { renderWithProviders } from '@/utils/test-utils';
global.matchMedia =
global.matchMedia ||
function () {
return {
addListener: jest.fn(),
removeListener: jest.fn(),
};
};
describe('Report', () => {
it('should render', () => {
renderWithProviders(
<Router>
<Report />
</Router>,
);
});
});
드디어 정상적으로 작동했습니다 🙂
global.matchMedia를 mocking하는 부분은 공통적으로 실행될 수 있는 영역에다가 넣어야겠다는 생각이 들었습니다.