TIL - 2024/06/17

박상우·2024년 6월 29일
0

📝 TIL

목록 보기
44/45
post-thumbnail

💫 트러블 슈팅

useRouter를 테스트 코드 내부에서 잘못 사용하고 있던 예시

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}`);
  });

Multiple Element

before

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("로그인이 필요합니다.");
      });
    });
  • 초기 상태를 로그인 상태로 가정하고 로그인 상태일 때 ‘로그아웃’ 버튼이 랜더링 됨을 우선 테스트한다.
  • 이후 mocking한 store를 통해 로그아웃 상황을 실행하고, 리랜더링한다.
  • 로그아웃된 상황에서 ‘로그인 ‘버튼이 보이는지 확인한다.

→ 하지만 로그아웃, 로그인 버튼이 둘다 보인다는 테스트 결과를 받게 되었음.

원인

  • render 함수 동작에 대한 오해 → render 동작을 전체 페이지를 리로드 하는 것 처럼 새로고침의 개념으로 생각하고 있었는데, 이전 rendering 되어있는 DOM에 추가로 요소를 덧붙이는 함수로 동작하고 있었다.

해결 방법

  • rerender 함수 활용 → 컴포넌트를 render 했을 때 return으로 rerender함수를 넘겨주는 것을 알게 되었다. → 직접 해당 요소를 다시 render하는 게 아니라 rerender()함수에 해당 요소를 인자로 넘겨주어 의도했던 새로고침을 사용할 수 있게 해야한다.
      → 기존 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("로그인이 필요합니다.");
    });
    위 코드처럼 수정했을 때 비로소 테스트를 통과할 수 있었다

TDD를 해보니

  • 물리적인 코드량이 많아져서 몸이 힘들긴함
  • 테스트 코드에 대한 확신이 아직 없고, 얼마나 디테일하게 테스트해야하는가에 대한 모호함이 아직은 존재한다.
  • 하지만 확실히 이전보다 화면을 직접 띄워놓고 비교하며 구현을 하는 일이 없어지다시피 했음. → 테스트 케이스를 따라가다보니 해당 케이스를 모두 만족한다면 컴포넌트가 완성됐다고 여겨도 될 것 같음.
  • 중복되는 코드가 많이보여서 효율적으로 테스트 코드를 작성할 수 있는 방법에 대한 고민이 필요하다.
profile
나도 잘하고 싶다..!

0개의 댓글

관련 채용 정보