inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트

3.3. 리액트 훅 테스트(feat. act 함수)

안녕하세요. 훅 테스트 질문이 있습니다!

해결된 질문

315

jack

작성한 질문수 1

0

예제에서 말씀해주신것처럼,

result.current.setState() 를 호출해서 act() 를 통해 업데이트 된 상태를 검증하는 방법을 말씀해주셨는데요.

 

훅 내부 이펙트에서 상태를 업데이트하는 로직을 검증하려면 어떤 식으로 검증해야할까요?

 

export const useDarkMode = (defaultTheme = THEME["LIGHT"]) => {
  const [theme, setTheme] = useState(defaultTheme);

  const changeTheme = (type: keyof typeof THEME) => {
    setTheme(THEME[type]);
  };

  useLayoutEffect(() => {
    const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");

    const changeListener = ({ matches }: MediaQueryListEvent) => {
      setTheme(THEME[matches ? "DARK" : "LIGHT"]);
    };

    mediaQueryList.addEventListener("change", changeListener);

    return () => {
      mediaQueryList.removeEventListener("change", changeListener);
    };
  }, []);

  return {
    theme,
    changeTheme,
  };
};

 

위 useDarkMode() 훅 내부 useLayoutEffect() 에서

window.matchMedia 의 change 이벤트를 감지하면, setTheme() 하도록 설계되어 있는데요.

 

window.matchMedia 함수의 matches 결과를 true 로 모킹하고,

window.matchMedia.dispatchEvent('change') 를 일으켜 검증을 시도해보았는데요.

생각처럼 검증이 되지 않는 것 같습니다.ㅠ

 

혹시 이렇게 검증을 시도하는 것이 맞는지. 검증하는게 맞는지. 여쭤봐도 될까요? 감사합니다.

javascript react 소프트웨어-테스트 vitest

답변 2

1

jack

자세히 설명해주셔서 감사합니다. 너무나 잘 이해되었습니다!

말씀해주신대로 스토리북을 활용하는 방법이 더 깔끔하다고 생각이 되어지네요.

좋은 강의에 항상 감사드립니다 🙂

1

코드 조커, 오프

안녕하세요 jack님!

우선 보통 effect내에서 상태를 직접 변경하는 경우(effect 내에서 setTheme 직접 호출)에는 별도 작업없이 result.current.theme만 체크해도 변경된 상태값을 기준으로 검증할 수 있는대요.

하지만 말씀하신 코드에서는 effect 함수 내에서 mediaQueryListchange 이벤트 등록 리스너만 실행됩니다. 그리고 이후에 change 이벤트가 발생했을때만 theme의 상태가 변경되도록 작성되어 있습니다.

이런 경우 원하는 matches 값 기준으로 이벤트 리스너를 등록하고 실행시켜야 하기 때문에 아래 정도로 window.matchMedia의 구현 자체에 모킹이 필요합니다.

const mockMatchMedia = matches => {
  let targetListener;
  // 이벤트 등록 핸들러 스텁
  const stubAddEventListener = (eventName, listener) => {
    targetListener = () => listener({ matches });
  };

  // 이벤트 제거 핸들러 스텁
  const stubRemoveEventListener = () => {
    targetListener = null;
  };

  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: vi.fn().mockImplementation(query => ({
      matches,
      media: query,
      onchange: null,
      addListener: vi.fn(), // deprecated
      removeListener: vi.fn(), // deprecated
      addEventListener: stubAddEventListener,
      removeEventListener: stubRemoveEventListener,
      dispatchEvent: vi.fn(),
    })),
  });
  // 이벤트 핸들러 강제 실행을 위한 dispatchEvent
  return { dispatchEvent: () => targetListener() };
};

// window.matchMedia에 대한 구현을 이전 설정으로 되돌린다.
afterEach(() => {
  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: vi.fn().mockImplementation(query => ({
      matches: false,
      media: query,
      onchange: null,
      addListener: vi.fn(), // deprecated
      removeListener: vi.fn(), // deprecated
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
      dispatchEvent: vi.fn(),
    })),
  });
});

it('theme가 dark 가 된다.', () => {
  const { dispatchEvent } = mockMatchMedia(true);
  const { result } = renderHook(() => useDarkMode());

  act(() => {
    dispatchEvent();
  });

  expect(result.current.theme).toBe('dark');
});

위 코드처럼 window.matchMedia 함수의 메커니즘에 필요한 이벤트 관련 API들을 모두모킹해야 비로소 원하는 시나리오대로 테스트를 작성할 수 있습니다.

(node.js 환경에서는 미디어 쿼리를 전혀 지원하지 않기 때문에 이렇게 원하는 시나리오를 검증할 수 있는 별도 구현체를 만들어야만 확인이 가능합니다.)

개인적으로 단위 테스트에서 이렇게 미디어 쿼리와 관련된 모듈 모킹을 통해 테스트하기보다는.. 스토리북내에서 실제 브라우저의 미디어 쿼리와 연동하여 useDarkModetheme 상태가 제대로 반영되는지 확인하는 것이 더 정확하고 깔끔한 방법일 것 같습니다..!

혹시 더 궁금하거나 다른 의견이 있으시면 얼마든지 답글 남겨주세요.

감사합니다 🙂

mockZustand

0

66

2

프로젝트 세팅 오류 및 버전 오류 문제 문의

0

95

2

toggleIsModalOpened 테스트 할때 궁금한 점이 있습니다.

0

42

2

로딩/에러처리 검증은 어떻게 하는게 좋을까요?

0

65

1

통합 테스트에서 API 요청에 대한 검증은 이루어지지 않아도 괜찮을까요?

0

69

2

lint에러..

0

72

1

단언문 순서에 따라 테스트 결과가 왜 달라지나요?

0

108

2

useNavigate 테스트 시, 검증 대상 질문입니다.

0

113

2

강의 예시프로젝트 업데이트좀 부탁드립니다.

2

165

2

통합테스트와 단위테스트 파일 분리

0

108

1

grid 양옆에 margin은 어디서 설정되어있는건가요 ?

0

131

1

vitest 설치했는데

0

206

2

2부 할인쿠폰 관련

0

176

1

에러 해결 방법

0

406

2

직접 구현한 atom 컴포넌트 테스트 범위 질문

0

175

1

setup, teardown 동작 순서

0

211

1

debounce 함수 테스트 정확도 관련 질문입니다.

0

176

2

ProductFilter test 어떤 방식이 더 선호되는 방식일까요?

0

204

2

통합 테스트 작성 방식에 대해 궁금한 점이 있습니다

0

258

2

vitest Extension 알려주세요.

0

267

1

2.1 강의 질문있습니다.

0

139

1

useNavigate()을 검증할 때 이해가 안되는 부분이 있습니다.

0

245

2

toHaveStyle 메서드 사용이 조금 이상한 것 같습니다.

0

194

2

TestPayment에 쿠폰 정보를 prop으로 전달하는 이유

0

184

2