Chapter 3-1. 프론트엔드 테스트 코드: 회고

한칙촉·2025년 9월 6일
post-thumbnail

3-1. 프론트엔드 테스트 코드

너무나 겁먹었던 테스트 코드 챕터... 간단히 기록해보자


WIL

✅ 테스트 코드의 흐름

1. 렌더링하기

  • render - 렌더링 수행
  • screen - 렌더링된 화면처럼 인터페이스가 구현된 객체에 접근
import { render, screen } from '@testing-library/react';

test('should show login form', () => {
  render(<Login />)
  
  // 화면에서 Username 레이블 텍스트를 가진 요소 가져옴
  const input = screen.getByLabelText('Username');
})

2. 사용자 인터렉션

  • fireEvent – 이벤트를 직접 디스패치, 일부 특수 이벤트나 userEvent 미지원 시 사용
  • userEvent – 실제 사용자가 상호작용하는 것처럼 이벤트 시뮬레이션
  • userEvent.setup() – 테스트마다 독립된 user 인스턴스를 생성, 연속적인 사용자 동작을 정확히 시뮬레이션

  • user.click(element) – 요소 클릭
  • user.dblClick(element) – 더블 클릭
  • user.hover(element) – 요소 위로 마우스 이동
  • user.unhover(element) – 요소에서 마우스 이동
  • user.type(element, "text") – 입력 필드에 텍스트 입력
  • user.clear(element) – 입력 필드 초기화
  • user.keyboard("text") – 키보드 입력 시뮬레이션
  • user.tab() – 탭키로 포커스 이동
  • user.selectOptions(selectElement, "value") – 셀렉트 박스 선택
it('기존 일정의 세부 정보를 수정하고 변경사항이 정확히 반영된다', async () => {
  setupMockHandlerUpdating();
  const { user } = setup(<App />);

  const editButtons = await screen.findAllByRole('button', { name: 'Edit event' });
  const titleInput = screen.getByLabelText('제목');

  await user.click(editButtons[0]); // 수정 버튼 클릭

  expect(screen.getByRole('button', { name: '일정 수정' })).toBeInTheDocument();
  expect(screen.getByDisplayValue('기존 회의')).toBeInTheDocument();

  await user.clear(titleInput); // 입력 필드 값 초기화
  await user.type(titleInput, '기존 회의3'); // 입력 필드에 '기존 회의3' 텍스트 입력
  await user.click(screen.getByRole('button', { name: '일정 수정' })); // 일정 수정 버튼 클릭

  const eventList = within(screen.getByTestId('event-list'));
  expect(await eventList.findByText('기존 회의3')).toBeInTheDocument();
});

3. 대상 가져오기

  • 단일 요소 가져오기
쿼리 유형0개 일치1개 일치1개 이상 일치재시도(비동기)
getBy...오류 발생요소 반환오류 발생x
queryBy...null 반환요소 반환오류 발생x
findBy...오류 발생요소 반환오류 발생o
  • 다중 요소 가져오기
쿼리 유형0개 일치1개 일치1개 이상 일치재시도(비동기)
getAllBy...오류 발생배열 반환배열 반환x
queryAllBy...[] 반환배열 반환배열 반환x
findAllBy...오류 발생배열 반환배열 반환o

  • getByRole - 접근성 트리에서 role을 기반으로 가져옴
test('', () => {
	screen.getByRole('button');
});
  • getByTestId test용 id 속성을 넣어 가져올 수 있음
test('', () => {
	// data-testid="test-button" 속성을 가진 요소
	screen.getByTestId('test-button');
});
  • getByText - 텍스트를 기반으로 가져옴
// <div>Hello World</div>

screen.getByText('Hello World') // 텍스트가 매치되는 요소
screen.getByText('llo Worl', {exact: false}) // 일부만 매치
screen.getByText(/World/) // 정규식 활용
screen.getByText((content, element) => content.startsWith('Hello')) // 함수로 가져옴

✅ 자주 쓰이는 API

  • beforeEach – 각 테스트 실행 전 공통 준비
  • afterEach – 각 테스트 실행 후 정리
  • beforeAll – 모든 테스트 시작 전 한 번 실행
  • afterAll – 모든 테스트 종료 후 한 번 실행

  • vi.fn() – 모의 함수 생성
  • vi.mock() – 모듈의 모의화
  • vi.spyOn() – 객체의 메서드를 감시
  • vi.waitFor() – 비동기 작업이 완료될 때까지 기다림

+) 🤜 과제를 통해 알게된 점.. 🥸

// src/__mocks__/handlersUtils.ts
export const setupMockHandlerDeletion = () => {
  const mockEvents: Event[] = [
    {
      id: '1',
      title: '삭제할 이벤트',
      date: '2025-10-15',
      startTime: '09:00',
      endTime: '10:00',
      description: '삭제할 이벤트입니다',
      location: '어딘가',
      category: '기타',
      repeat: { type: 'none', interval: 0 },
      notificationTime: 10,
    },
  ];
  server.use(
    http.get('/api/events', () => {
      return HttpResponse.json({ events: mockEvents });
    }),
    http.delete('/api/events/:id', ({ params }) => {
      const { id } = params;
      const index = mockEvents.findIndex((event) => event.id === id);
      mockEvents.splice(index, 1);
      return new HttpResponse(null, { status: 204 });
    })
  );
};

// src/__tests__medium.integration.spec.tsx
it("네트워크 오류 시 '일정 삭제 실패'라는 텍스트가 노출되며 이벤트 삭제가 실패해야 한다", async () => {
  setupMockHandlerDeletion();

  // 삭제 처리가 실패하도록 덮어씀
  server.use(
    http.delete('/api/events/:id', () => {
      return HttpResponse.error();
    })
  );

  const { result } = renderHook(() => useEventOperations(true));

  await act(async () => {
    result.current.deleteEvent('1');
  });

  expect(enqueueSnackbarFn).toHaveBeenCalledWith('일정 삭제 실패', { variant: 'error' });
});

우선 각 테스트에서 사용되는 목 데이터와 서버 처리 로직을 하나의 함수로 묶어서 모킹한 부분이 되게 인상적이었다... 반복되는 목 데이터 재사용도 가능하고 엄청 깔끔해보였다. 하나의 함수를 여러 개의 테스트에 적용할 수 있으니까!!

그리고 처음 네트워크 오류 처리를 다루는 테스트를 접하고 도대체 어떻게 작성해야 하는건지 상상도 되지 않았다... 이러한 특수한 상황도 테스트가 가능하다는 사실을 처음 알게 되었다. 실제 환경에서 발생할 수 있는 여러 케이스를 시뮬레이션하고, ui 반응이나 오류 처리 로직을 검증할 수 있다는게 놀라웠다. 👍

KTP

Keep

꾸준히... 과제를 하자

Problem

역시 경험이 많이 없다보니 너무 생소하게 느껴졌다... 앞으로 테스트 코드와 더 친해져보자..

Try

개인 플젝에 테스트 코드 도입해보기

profile
빙글빙글돌아가는..

0개의 댓글