describe("Board Row Component Test", () => {
beforeEach(() => {
render(
<BoardRow
title={mockRowData.title}
content={mockRowData.content}
userId={mockRowData.userId}
postId={mockRowData.postId}
upload={mockRowData.upload}
count={mockRowData.count}
/>
);
});
...
test("게시글 행을 클릭하면 해당 게시글의 상세 페이지로 이동한다.", () => {
const push = jest.fn();
// useRouter의 타입을 올바르게 설정하여 모킹
(useRouter as jest.Mock).mockImplementation(() => ({
push,
}));
const row = screen.getByTestId("row");
fireEvent.click(row);
expect(push).toHaveBeenCalledWith(`/post/${mockRowData.postId}`);
});
});
게시글 행을 클릭하면 해당 게시글의 상세 페이지로 이동한다.
라는 테스트는 useRouter를 사용한 동작을 테스트하기 때문에 useRouter에 대한 Mocking과 Push 동작에 대한 mocking이 필요하다.
위 코드에서 해당 테스트에서만 useRouter를 사용하기 때문에 테스트 케이스 내부에서 useRouter를 mocking해준 후 테스트를 돌렸을 때 위의 에러를 마주쳤다.
문제는 useRouter와 render함수의 우선순위 관계라고 생각했다.
랜더링 로직과 Mocking 우선순위를 따져보면, beforeEach 내부에 컴포넌트가 랜더링 된 이후에 useRouter를 mocking 한다. 이미 랜더링 된 상태에서 useRouter의 동작을 mocking 함수로 설정하고 있기 때문에 원하는 방식으로 테스트가 진행되고 있지 않았던 것 같다.
그래서 아래와 같이 mocking 함수의 위치를 옮겨주거나, 컴포넌트를 다시 랜더링하는 방식을 사용하면 해결할 수 있다.
test("게시글 행을 클릭하면 해당 게시글의 상세 페이지로 이동한다.", () => {
const push = jest.fn();
// useRouter의 타입을 올바르게 설정하여 모킹
(useRouter as jest.Mock).mockImplementation(() => ({
push,
}));
// 다시 컴포넌트를 랜더링 한다.
render(
<BoardRow
title={mockRowData.title}
content={mockRowData.content}
userId={mockRowData.userId}
postId={mockRowData.postId}
upload={mockRowData.upload}
count={mockRowData.count}
/>
);
const row = screen.getByTestId("row");
fireEvent.click(row);
expect(push).toHaveBeenCalledWith(`/post/${mockRowData.postId}`);
});
describe("Header Test", () => {
beforeEach(() => {
render(
<UserInfoStoreContext.Provider value={mockStore}>
<Header />
</UserInfoStoreContext.Provider>
);
});
test("초기 랜더링 - 로그인이 되어있지 않은 경우 '로그인이 필요합니다.'문구가 보인다.", async () => {
let userIdElement = screen.getByTestId("header-info");
// Mocking 상태를 로그아웃 상태로 변경
await act(async () => {
mockStore.setState({ id: "" });
});
render(
<UserInfoStoreContext.Provider value={mockStore}>
<Header />
</UserInfoStoreContext.Provider>
);
screen.getByTestId("header-info");
expect(userIdElement).toHaveTextContent("로그인이 필요합니다.");
});
});
→ 하지만 로그아웃, 로그인 버튼이 둘다 보인다는 테스트 결과를 받게 되었음.
→ 기존 beforeEach()로 요소를 랜더링하는 방식은 rerender 함수를 전달 받을 수 없기 때문에 파일 상단에서 wrapper 함수로 감싼후 해당 함수를 재사용할 수 있도록 수정해주었다.
const renderHeader = () => {
return render(
<UserInfoStoreContext.Provider value={mockStore}>
<Header />
</UserInfoStoreContext.Provider>
);
};
test("초기 랜더링 - 로그인이 되어있지 않은 경우 '로그인이 필요합니다.'문구가 보인다.", async () => {
const { rerender } = renderHeader();
let userIdElement = screen.getByTestId("header-info");
// Mocking 상태를 로그아웃 상태로 변경
await act(async () => {
mockStore.setState({ id: "" });
});
rerender(
<UserInfoStoreContext.Provider value={mockStore}>
<Header />
</UserInfoStoreContext.Provider>
);
screen.getByTestId("header-info");
expect(userIdElement).toHaveTextContent("로그인이 필요합니다.");
});
위 코드처럼 수정했을 때 비로소 테스트를 통과할 수 있었다