suhyeon57 님의 상세페이지[6팀 김수현] Chapter 3-1. 프론트엔드 테스트 코드

Medium

7주차 과제 체크포인트

기본과제

Medium

  • 총 11개의 파일, 115개의 단위 테스트를 무사히 작성하고 통과시킨다.

질문

Q. medium.useEventOperations.spec.tsx > 아래 toastFn과 mock과 이 fn은 무엇을 해줄까요?

mocking을 사용하는 이유 --> 실제 DB나 외부 환경에 영향을 주지 않고, 독립적이고 빠른 테스트를 하기 위함입니다.

vi.fn()은 호출 여부와 인자를 추적할 수 있는 mock 함수를 만들 때 사용합니다.

vi.mock('notistack', …)은 notistack 모듈을 테스트 환경에서 가짜 버전으로 바꿔치기하는데, 이렇게 하면 useSnackbar가 반환하는 enqueueSnackbar가 실제 스낵바를 띄우는 대신, 우리가 만든 mock 함수(toastFn)를 실행하게 됩니다.

따라서, 테스트에서는 expect(toastFn).toHaveBeenCalledWith('일정 삭제 실패', { variant: 'error' });처럼 UI 동작 대신 호출 자체와 전달된 메시지만 검증할 수 있습니다.

따라서, 테스트에서 expect(enqueueSnackbarFn).toHaveBeenCalledWith('일정 삭제 실패', { variant: 'error' }); 같은 검증을 할 수 있다.

Q. medium.integration.spec.tsx > 여기서 ChakraProvider로 묶어주는 동작은 의미있을까요? 있다면 어떤 의미일까요?

네, 의미가 있습니다. 테스트는 실제 동작하는 화면과 최대한 유사해야 정확해집니다.

ChakraProvider는 Chakra UI의 테마와 context를 제공하기 때문에, 이를 테스트에서 함께 적용하면 실제 UI 환경과 동일한 조건에서 컴포넌트를 검증할 수 있습니다.

Q. handlersUtils > 아래 여러가지 use 함수는 어떤 역할을 할까요? 어떻게 사용될 수 있을까요?

Q. setupTests.ts > 왜 이 시간을 설정해주는 걸까요?

심화 과제

  • App 컴포넌트 적절한 단위의 컴포넌트, 훅, 유틸 함수로 분리했는가?
  • 해당 모듈들에 대한 적절한 테스트를 5개 이상 작성했는가?

과제 셀프회고

테스트 코드 작성 후 통과하면 짜릿하다는 기분을 느낄 수 있다고 했는데 정말 느꼈습니다.. ai를 사용하지 않고 함수들이 어디에서 어떻게 쓰이는지 직접 확인해 가면서 테코를 작성하니 시간이 많이 걸렸지만, 재미를 느꼈던 것 같습니다.

실무에서는 테코를 작성하지 않아서 항상 코드를 수정, 추가하려고 할 때 다른 곳에서 에러가 나지 않을까 생각하며 조심스럽게 수정해나가는데 테코를 작성하면 마음 놓고 수정할 수 있지 있겠다라고 생각했고, 시간이 된다면 실무 테코도 작성해보고 싶습니다.

기술적 성장

renderHook 사용 경험

처음에 커스텀 훅을 테스트하려고 했을 때, 훅을 직접 호출했더니 오류가 발생했습니다. 검색해보니, 테스트 환경에서는 훅을 직접 호출할 수 없고, react-testing-library의 renderHook을 사용해야 한다는 것을 알게 되었습니다.

renderHook은 훅을 호출하고, 훅이 반환하는 현재 값과 메서드를 result.current를 통해 접근할 수 있도록 해줍니다. 이를 통해 훅의 상태와 메서드를 안전하게 테스트하며, 원하는 동작을 검증할 수 있었습니다.

act ()의 사용

테스트 코드에서 훅을 검증하던 중 navigate('prev')를 호출했을 때, 상태가 업데이트되지 않는 문제가 있었습니다.

const { result } = renderHook(() => useCalendarView());

result.current.navigate('prev'); 

const date1 = result.current.currentDate;
const date2 = new Date('2025-09-01T00:00:00.000Z'); 
assertDate(date1, date2);

처음에는 단순히 result.current.navigate('prev')만 호출하면 currentDate가 즉시 바뀔 거라고 생각했습니다. 하지만 실제로는 리액트 상태 업데이트가 비동기적이며, Testing Library는 모든 상태 변경을 act()로 감싸야 안전하게 반영된다는 규칙이 있었습니다.

그래서 아래처럼 act()로 감싸주자 정상적으로 동작했습니다.

const { result } = renderHook(() => useCalendarView());
  act(() => {
    result.current.navigate('prev');
  });
  const date1 = result.current.currentDate;
  const date2 = new Date('2025-09-01T00:00:00.000Z');
  assertDate(date1, date2);

위 내용처럼 리액트 테스트 환경에서는 모든 상태 변경이 act 내부에서 실행되어야 한다는 걸 알았습니다.

코드 품질

//********************다시 작성 필요******************/
it('정의된 이벤트 정보를 기준으로 적절하게 저장이 된다', async () => {
  const initialEvents: Event[] = [
    {
      id: '1',
      title: '기존 회의',
      date: '2025-07-01',
      startTime: '09:00',
      endTime: '10:00',
      description: '기존 팀 미팅',
      location: '회의실 B',
      category: '업무',
      repeat: { type: 'none', interval: 0 },
      notificationTime: 10,
    },
  ];
  setupMockHandlerCreation(initialEvents);
  const { result } = renderHook(() => useEventOperations(false));
  const newEvent: Event = {
    id: '',
    title: '신규 세미나',
    date: '2025-07-10',
    startTime: '11:00',
    endTime: '12:00',
    description: '신규 프로젝트 세미나',
    location: '회의실 A',
    category: '업무',
    repeat: { type: 'none', interval: 0 },
    notificationTime: 10,
  };

  await act(async () => {
    await result.current.saveEvent(newEvent);
  });

  await waitFor(() => {
    expect(result.current.events).toEqual([initialEvents[0], { ...newEvent, id: '2' }]);
  });
});

이 테스트에 대한 이해가 맞는지 의문이 들어서 다시 리팩토링 해보고 싶습니다.

학습 효과 분석

과제 피드백

과제를 통해 테스트 코드에 대한 기본적인 이해가 많이 생겼습니다. 사전 스터디 때는 테스트 코드의 이론적인 부분만 접했기 때문에 깊게 다루지 못했지만, 이번 과제를 통해 직접 작성하고 실행해보면서 테스트가 실제로 어떻게 동작하는지를 명확하게 경험할 수 있어 좋았습니다.

리뷰 받고 싶은 내용

import { fetchHolidays } from '../../apis/fetchHolidays';
describe('fetchHolidays', () => {
  it('주어진 3월의 공휴일 삼일절을 반환한다', () => {
    expect(fetchHolidays(new Date('2025-03-01'))).toEqual({
      '2025-03-01': '삼일절',
    });
  });
  it('공휴일이 없는 7월에 대해 빈 객체를 반환한다', () => {
    expect(fetchHolidays(new Date('2025-07-01'))).toEqual({});
  });

  it('여러 공휴일이 있는 10월에 대해 모든 공휴일을 반환한다', () => {
    expect(fetchHolidays(new Date('2025-10-05'))).toEqual({
      '2025-10-03': '개천절',
      '2025-10-05': '추석',
      '2025-10-06': '추석',
      '2025-10-07': '추석',
      '2025-10-09': '한글날',
    });
  });
});

이 테코를 진행할 때 만약 휴일이 추가될 경우 테스트 코드 에러가 날텐데 그런 경우를 대비해서 작성할 수 있는 방법이 있을까요?

과제 피드백

수고했습니다. 이번 과제는 테스트를 하는 다양한 방법들을 접하면서 테스트 코드를 작성한다라는 그 심리적 장벽을 허물고 친숙해지는데 있었습니다. "테스트 코드 작성 후 통과하면 짜릿하다는 기분을 느낄 수 있다고 했는데 정말 느꼈습니다.." ㅋㅋㅋ 충분히 심리적 장벽을 허물고 많이 친해진것 같아요. ㅎ

"ai를 사용하지 않고 함수들이 어디에서 어떻게 쓰이는지 직접 확인해 가면서 테코를 작성하니 시간이 많이 걸렸지만, 재미를 느꼈던 것 같습니다." 좋은 접근이었습니다. 직접 코드를 따라가면서 이해하는 과정이 시간은 오래 걸리지만 더 깊은 이해를 가져다주죠. 뇌는 에너지를 효율적으로 쓰고 싶어서 의미가 있는 정보를 선별적으로 보관하려 하는데, 애써 하려고 한다는 그 의식적 노력과 고통을 증거로 삼으니까요. 고생했어요! 잘했습니다.

"실무에서는 테코를 작성하지 않아서 항상 코드를 수정, 추가하려고 할 때 다른 곳에서 에러가 나지 않을까 생각하며 조심스럽게 수정해나가는데 테코를 작성하면 마음 놓고 수정할 수 있지 있겠다" 정말 맞는 말입니다. 테스트 코드가 주는 가장 큰 가치 중 하나가 바로 이런 심리적 안정감이에요. 귀찮음의 허들이 처음에는 더 높겠지만 불안함의 해소가 귀찮음보다 더 커지는 순간이 올테니 꼭 실무에서도 한번의 허들을 넘어가보길 바랍니다.

Q) 이 테코를 진행할 때 만약 휴일이 추가될 경우 테스트 코드 에러가 날텐데 그런 경우를 대비해서 작성할 수 있는 방법이 있을까요?

=> 오히려 테스트가 깨져야 정상입니다. 휴일이 추가되었다는 건 기능이 변경된 것이고, 그에 맞춰 구현체도 수정되어야 하는 거죠. 테스트 코드가 깨진다는 건 "이 부분을 업데이트해야 한다"는 신호를 주는 거예요. 실제로 이런 상황에서는 새로운 휴일 데이터로 fetchHolidays 함수를 업데이트하고, 해당 테스트 케이스도 새로운 기대값으로 수정하면 됩니다.

=> 테스트는 변경사항을 감지해서 개발자에게 "여기 수정 필요해!"라고 알려주는 역할을 하는 거거든요. 변경사항이 생겼는데도 테스트가 통과한다면 그게 더 문제입니다. 테스트가 제 역할을 못하고 있다는 뜻이니까요.

"시간이 된다면 실무 테코도 작성해보고 싶습니다." 이런 마음가짐이 정말 좋네요. 일단 작은 것부터라도 하나씩 만들어보면서 그 가치를 직접 체험해보길 바라요. 수고하셨습니다.