Medium
7주차 과제 체크포인트
기본과제
Medium
- 총 11개의 파일, 115개의 단위 테스트를 무사히 작성하고 통과시킨다.
질문
Q. medium.useEventOperations.spec.tsx > 아래 toastFn과 mock과 이 fn은 무엇을 해줄까요?
- enqueueSnackbarFn : 가짜 함수 (spy)로 실제로 토스트를 띄우는 대신, 어떤 메시지로 몇 번 호출되었는지 기록만 합니다.
- vi.mock('notistack'...): useSnackBar()를 바꿔치기 해서 내부의 enqueueSnackbar가 toastFn을 가리키게 만듭니다.
// "호출되었는지, 어떤 인자로 호출되었는지"를 추적할 수 있는 mock 함수
const enqueueSnackbarFn = vi.fn();
// notistack 모듈을 통으로 mock하겠다
vi.mock('notistack', async () => {
// 실제 모듈(notistack)의 구현을 불러오자
const actual = await vi.importActual('notistack');
return {
// 실제 모듈의 export를 그대로 펼쳐 넣고,
...actual,
useSnackbar: () => ({
// useSnackbar 훅만 mock해서 호출을 추적할 수 있게함 - useSnackbar 훅만 덮어쓰기
enqueueSnackbar: enqueueSnackbarFn,
}),
};
});
Q. medium.integration.spec.tsx > 여기서 Provider로 묶어주는 동작은 의미있을까요? 있다면 어떤 의미일까요?
setup()에서 ThemeProvider, CssBaseline, SnackbarProvider로 감싸는 이유는 테스트에서도 운영 환경과 동일한 컨텍스트/스타일/DOM의 변화 등 을 재현하기 위해서입니다. 특히 통합 테스트에서는 컴포넌트가 테마 토큰(useTheme, useMediaQuery, sx), 전역 스타일(CssBaseline), 토스트 컨텍스트(useSnackbar)에 실제로 의존하므로, 프로덕션과 동일한 조건에서 동작을 검증하는 게 중요합니다.
운영과 동일한 css, 컨텍스트를 제공하기 위함이라면 모든 테스트 파일에 들어가면 좋지 않을까? 라는 생각이 스쳐지나갔는데.. 꼭 그렇지 않다고 보는 게 맞을 것 같습니다. 순수 함수나 작은 단위의 컴포넌트는 DOM이나 테마와 같은 컨텍스에 의존하지 않기 때문에 유닛 테스트와 같은 파일에서는 필요하지 않다고 생각이 들었습니다.
Q. handlersUtils > 아래 여러가지 use 함수는 어떤 역할을 할까요? 어떻게 사용될 수 있을까요?
- server.use : MSW 의 Node 테스트 서버에 요청 핸들러를 런타임에 추가/덮어쓸 수 있는 함수입니다. Node.js 런타임(=프로세스)가 살아있는 동안 서버 인스턴스에 계속 남아있어서 테스트 프로세스가 종료될까지 유지할 수 있습니다.
medium모드라 작성해주신 setup 함수를 살펴보면, 총 3가지로
- setupMockHandlerCreation
- setupMockHandlerUpdating
- setupMockHandlerDeletion
CUD에 필요한 목업데이터 생성 -> server.use를 통해 핸들러를 등록/대체 -> 성공/실패/에러 응답에 대한 시뮬레이션에 사용 가능하게 작성해주셨습니다 ㅎㅎ
프로세스가 종료될 수 있을 때까지 덮어쓴 채로 유지되기 때문에 setupTest.ts에 server.resetHandlers()를 afterEach에서 호출해서 다음 테스트로 넘어갈 때 적용되지 않도록 반영해야합니다.
코치님이 설정해주신 setupTest.ts 파일 부분
afterEach(() => {
// 각 테스트 후 핸들러 리셋시키기
server.resetHandlers();
vi.clearAllMocks();
});
Q. setupTests.ts vi.setSystemTime(new Date('2025-10-01')) > 왜 이 시간을 설정해주는 걸까요?
테스트의 오늘(now!)를 고정해 날짜/시간에 의존하는 로직을 '어디에서 혹은 누가' 돌려도 동일하게 재현하기 위해서라고 생각합니다. 사실 이건 테스트 작성하다가 놓쳤던..! 부분입니다. Event 객체를 만들어주는 함수를 만들면서 날짜를 8월에 맞게 진행했었는데 new Date()를 사용하다보면 2025-10-01로 고정이 되어있어서 설정을 찾다가 이걸 발견했습니다. (ㅎㅎ)
Q. setupTests.ts - expect.hasAssertions() > 설정하는 이유 ?
이 메서드에 대해 찾아보니, '이 테스트 안에서는 최소 1개의 expect가 반드시 호출되어야한다'를 보장하는 메서드였습니다. 비동기/타이머/이벤트 기반 검증에서 단언이 "실행되지 않고 지나가는" 실수를 자동으로 적발해 주는 안전 장치용으로 사용하는 메서드라고 생각합니다.
심화 과제
- App 컴포넌트 적절한 단위의 컴포넌트, 훅, 유틸 함수로 분리했는가?
- 해당 모듈들에 대한 적절한 테스트를 5개 이상 작성했는가?
과제 셀프회고
팀원들이 작성한 테스트코드는 자주 접해봤는데 실제로 작성해보는 건 처음이었습니다. (모든게 처음이라니..TT) 우선 easy 파일을 작성해서 문법과 api를 익혔습니다. 이후에는 이 라이브러리는 어떤 구조로 돌아가는 가를 제일 먼저 이해하고, 테스트 환경을 구성하는 것에 대해 어떤 설정이 필요한지 중점적으로 학습하고자 코치님이 작성해주신 파일들을 위주로 본 것 같아요! 테스트 코드를 작성하면서 제일 헤매고 시간이 많이 소요되었던 부분은 통합테스트였습니다. 실제 테마/CSS 컨텍스트가 반영된 DOM 값을 어떤 쿼리로 접근할지에 대한 고민을 많이 했던 것 같습니다. 막힐 때는 AI 도움과 팀원분들 PR을 참고해, 시나리오에 맞는 API를 선택하며 작업했습니다.
기술적 성장
act vs await
- await : 비동기 작업이 끝날때까지 기다리기 (eg. Promise)
- act : React 상태 업데이트가 일으키는 렌더/이펙트가 모두 반영될 때까지 테스트를 트랜잭션처럼 감싸기 -> hook의 유닛테스트 중 리액트의 상태나 이펙트, 렌더와 관련된 테스트는 act를 사용했고 서버 호출과 관련된 hook 혹은 통합 테스트에서는 await를 위주로 사용했습니다.
getByText vs queryByText
- getByText : 존재가 확실히 기대할 때 사용, 없으면 바로 에러 throw -> 테스트가 즉시 실패한다
- queryByText : 없으면 null 반환, 여러개 매칭되면 에러 throw -> 무조건 값이 있어야한다를 사용할때는 getBy를 사용했고, 값이 없어야한다를 사용할때는 queryByText를 썼습니다.
코드 품질
트러블슈팅 통합테스트에서 1초를 지날 경우 알람이 발생하는 것을 검증하는 알림 테스트를 작성중 , 다음과 같은 에러를 만났습니다.
Error: "setSystemTime" was called already and date was mocked. Reset timers using `vi.useRealTimers()` if you want to use fake timers again.
at FakeTimers.useFakeTimers (file:///Users/gyuwonlim/Desktop/project/hhPlusFE6/front_6th_chapter3-1/node_modules/.pnpm/vitest@3.2.4_@types+node@22.8.1_@vitest+ui@3.2.4_jsdom@26.1.0_msw@2.10.3_@types+node@22.8.1_typescript@5.6.3_/node_modules/vitest/dist/chunks/vi.bdSIJ99Y.js:3653:31)
- medium.integration.spec.tsx
...
beforeEach(() => {
vi.useFakeTimers({ shouldAdvanceTime: true });
});
이미 setupTest.ts에서 전역으로 userFakeTimers를 사용하고 있는 상태인데, 또 FakeTimers는 사용하려고 보니 충돌이 발생하여 문제가 되었습니다. AI찬스로 advanceTimersByTime를 알게되어 적용했습니다.
act(() => {
// 가짜 타이머를 1초만큼 이동시키고, 그로 인해 발생하는 React의 상태업데이트/이펙트를 act를 통해 반영
vi.advanceTimersByTime(1000);
});
과제 피드백
(제가 작성한 테스트 코드 중) 모호하거나 애매했던 부분
useEventForm의 테스트의 효과성 useEventForm에 대한 테스트를 심화 과제로 추가한 이유는, 폼 훅 자체의 입력/설정 흐름도 검증할 필요가 있다고 봤기 때문이었습니다. 다만 작성 과정에서 useEventOperations(네트워크/서버 호출)와 timeValidation(순수 검증)에서 이미 다루는 내용과 겹치는 지점이 많아 “중복 테스트 코드이지 않을까?”라는 의문이 생겼습니다. 제 자신 스스로 내린 결론은 useEventForm 테스트는 범위를 폼 훅 고유 책임으로 한정의 의미로 추가하자! 라는 생각으로 테스트 항목을 추가했습니다.
목업데이터 관리 easy파일을 작성하면서 sampleEvents 배열을 하나하나씩 작성해서 사용했는데, 사용하다가 관리가 되지 않아 createEventMock 함수를 임의로 생성해서 활용했습니다. 다만 임시로 만들다보니, 이 함수를 통해 만들 수도 있지만 id나 중복 데이터에 대한 관리가 제대로 되지 않을 것 같다는 생각이 들었습니다. 실제 업무에서도 임시 데이터를 하나만 두고 활용해서 썼었는데, 평소에 실무에서 목업데이터나 테스트에 사용하는 관련 데이터들을 어떻게 관리하시는지 궁금합니다.
과제 피드백
헉 규원님도 회고문서의 내용이 없네요 ㅜㅜ 사실 체크박스가 중요하긴 한데 코드를 살펴보니 코드를 대부분 작성한 것으로 보입니다. 그래서 일단 합격으로 드릴게요. 다음엔 회고문서도 꼭 작성하시면 좋을 것 같아요. 역랑을 궁극적으로 발전시키는데에는 동작하는 코드도 중요하지만 코드에대한 생각 정리와 과정에 대한 복기인 것 같습니다!