해결된 질문
작성
·
226
·
수정됨
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') 를 일으켜 검증을 시도해보았는데요.
생각처럼 검증이 되지 않는 것 같습니다.ㅠ
혹시 이렇게 검증을 시도하는 것이 맞는지. 검증하는게 맞는지. 여쭤봐도 될까요? 감사합니다.
답변 2
1
1
안녕하세요 jack님!
우선 보통 effect내에서 상태를 직접 변경하는 경우(effect 내에서 setTheme 직접 호출)에는 별도 작업없이 result.current.theme
만 체크해도 변경된 상태값을 기준으로 검증할 수 있는대요.
하지만 말씀하신 코드에서는 effect 함수 내에서 mediaQueryList
의 change
이벤트 등록 리스너만 실행됩니다. 그리고 이후에 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 환경에서는 미디어 쿼리를 전혀 지원하지 않기 때문에 이렇게 원하는 시나리오를 검증할 수 있는 별도 구현체를 만들어야만 확인이 가능합니다.)
개인적으로 단위 테스트에서 이렇게 미디어 쿼리와 관련된 모듈 모킹을 통해 테스트하기보다는.. 스토리북내에서 실제 브라우저의 미디어 쿼리와 연동하여 useDarkMode
의 theme
상태가 제대로 반영되는지 확인하는 것이 더 정확하고 깔끔한 방법일 것 같습니다..!
혹시 더 궁금하거나 다른 의견이 있으시면 얼마든지 답글 남겨주세요.
감사합니다 🙂