useDidMountuseDidMount : 컴포넌트가 마운트 되었을 때 주어진 callback 함수를 실행하는 훅
<aside> 💡 mount(마운트)
: 컴포넌트가 처음 화면에 렌더링될 때
</aside>
const useDidMount = (callback: VoidFunction) => {
const didMountRef = useRef<boolean>(false);
useEffect(() => {
if (didMountRef.current) return;
didMountRef.current = true;
callback();
}, []);
};
export default useDidMount;
useRef훅을 통해 생성한 ref참조 변수의 초기값은 false. useEffect를 통해 mount되었음을 감지했을 때 ref.current값을 true로 변경하며 callback함수 실행useDidMount.test.ts 전체 코드
useDidMount.test.ts에 대한 설명
describe('useDidMount', () => { ... });
describe : 여러 관련 테스트들을 그룹화하는 역할useDidMount에 관한 테스트 케이스들을 그룹화한다default export test
it('default export이여야 한다', () => {
expect(useDidMount).toBeDefined();
});
useDidMount가 정의되어 있는지 확인effectCallback 실행 test
it('effectCallback이 실행되어야 한다', () => {
const effectCallback = jest.fn();
renderHook(() => useDidMount(effectCallback));
expect(effectCallback).toBeCalled();
});
renderHook 함수를 사용하여 useDidMount hook을 실행하며, 이때 effectCallback을 인자로 전달
jest.fn()을 사용하여 가짜 함수 (mock function)를 생성하여 전달expect 함수를 사용하여 effectCallback이 호출되었는지 검증rerender 시 실행 횟수 test
it('rerender 시 1번 실행되어야 한다', () => {
const effectCallback = jest.fn();
const { rerender } = renderHook(() => useDidMount(effectCallback));
rerender();
expect(effectCallback).toBeCalledTimes(1);
});
effectCallback이 한 번만 호출되어야 함을 확인useDidMount Component test
describe('useDidMount Component', () => { ... });
App) 내에서 useDidMount hook의 동작을 검증하는 테스트 케이스들을 정의App 컴포넌트 정의
useState와 useDidMount를 사용하는 간단한 컴포넌트모든 테스트 후 mock 함수 초기화
afterEach(() => {
jest.clearAllMocks();
});
App 컴포넌트 테스트
useDidMount hook이 App 컴포넌트에서 제대로 동작하는지, 그리고 상태가 변화해도 mockCallback이 한 번만 호출되는지 확인하는 테스트 케이스들임useDebounce// src/hooks/useDebounce.ts
export default function useDebounce(value: string, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
useDeboune.test.ts 전체 코드useDeboune.test.ts에 대한 설명setTimeout, setInterval, clearTimeout, clearInterval을 가짜로 만들어준다.
describe("useDebounce", () => {
// ..
jest.useFakeTimers();
}
즉시 실행했을 때
describe("useDebounce", () => {
// ..
it("동일한 값을 즉시 반환해야 한다", () => {
const { result } = renderHook(() => useDebounce("test", 300));
expect(result.current).toBe("test");
});
}
renderHook을 이용하여 훅을 실행한다.
이 때, debouncedValue는 result.current에 들어가게 된다.
값이 변경되지 않았을 때
describe("useDebounce", () => {
// ..
it("값이 변경되지 않았다면 지연 후에도 동일한 값을 반환해야 한다", () => {
const { result } = renderHook(() => useDebounce("test", 300));
act(() => {
jest.advanceTimersByTime(300);
});
expect(result.current).toBe("test");
});
}
jest.advanceTimersByTime();를 이용해서 300ms시간을 흘려 보냈다.
하지만, 그 사이에 값 변동이 없었기 때문에 동일한 값을 return한다.
값을 변경했을 때
describe("useDebounce", () => {
// ..
it("지연 시간이 지나기 전까지는 디바운스된 값이 업데이트되지 않아야 한다", () => {
const { result, rerender } = renderHook(
({ value }) => useDebounce(value, 300),
{
initialProps: { value: "initial" }
}
);
rerender({ value: "updated" });
act(() => {
jest.advanceTimersByTime(250);
});
expect(result.current).toBe("initial");
act(() => {
jest.advanceTimersByTime(50);
});
expect(result.current).toBe("updated");
});
}
renderHook을 실행하는데, 두 번째 인자의 initiaProps에 value로 initial을 넣는다. 그리고, value를 updated로 리렌더링을 수행한다.
250ms가 지났을 때는 여전히 initial이지만, 50ms가 더 지났을 때는 updated로 값이 변경되어 있다.