Vitest와 Testing Library로 견고한 컴포넌트 구축하기
Lukas Schneider
DevOps Engineer · Leapcell

소개
프론트엔드 개발의 빠르게 진화하는 환경에서 컴포넌트의 신뢰성과 유지보수성을 보장하는 것이 무엇보다 중요합니다. 사용자 인터페이스가 점점 더 복잡해짐에 따라 강력한 테스트 전략의 필요성이 그 어느 때보다 중요해지고 있습니다. 단위 및 상호작용 테스트는 버그를 조기에 발견하고, 예상 동작을 문서화하며, 자신감 있는 리팩토링을 가능하게 하는 첫 번째 방어선 역할을 합니다. 이 글에서는 두 가지 강력한 도구인 Vitest와 Testing Library를 어떻게 완벽하게 통합하여 사용자 상호작용을 반영하는 효과적인 테스트를 작성하는 실용적인 단계를 안내하면서 컴포넌트 테스트 게임을 향상시킬 수 있는지 살펴봅니다.
테스트 생태계 이해하기
실제 구현에 들어가기 전에 논의의 기초가 될 몇 가지 핵심 개념을 명확히 해봅시다.
단위 테스트: 개별 함수, 클래스 또는 이의 경우 컴포넌트와 같이 격리된 코드 단위를 테스트하여 예상대로 작동하는지 확인하는 데 중점을 둡니다. 목표는 애플리케이션의 가장 작은 테스트 가능한 부분을 독립적으로 검증하는 것입니다.
상호작용 테스트 (또는 통합 테스트): 애플리케이션의 다른 부분 또는 단위가 올바르게 함께 작동하는지 확인합니다. 프론트엔드 컴포넌트의 경우 이는 사용자 상호작용(클릭, 입력 변경 등)을 시뮬레이션하고 컴포넌트가 적절하게 응답하고 UI 변경 사항을 반영하는지 단언하는 것을 의미합니다.
Vitest: Vite를 기반으로 구축된 현대적이고 매우 빠른 테스터입니다. Jest와 호환되는 익숙한 API, 최고 수준의 TypeScript 지원, 즉각적인 핫 모듈 리로딩을 제공하여 프론트엔드 개발자의 테스트 경험을 훨씬 더 즐겁고 효율적으로 만듭니다.
Testing Library: 사용자가 애플리케이션과 상호작용하는 방식을 모방하여 UI 컴포넌트를 테스트하는 데 도움이 되는 유틸리티 모음입니다. 핵심 철학은 컴포넌트 구현 세부 정보에 집중하기보다는 사용자가 DOM을 쿼리하는 방식으로 접근성과 강력한 테스트를 우선시하는 것입니다. 이를 통해 테스트는 리팩토링에 더 탄력적이고 실제 사용자 기대치와 더 일치하게 됩니다.
효과적인 컴포넌트 테스트 만들기
Vitest와 Testing Library가 어떻게 조화롭게 작동하여 간단한 React 컴포넌트에 대한 강력한 테스트를 만드는지 살펴보겠습니다. 예제에서는 React를 사용하지만, Vue 또는 Svelte와 같은 다른 프레임워크에도 원칙이 대부분 적용됩니다.
텍스트를 표시하고 클릭 시 onClick
핸들러를 트리거하는 간단한 Button
컴포넌트를 생각해 봅시다.
// components/Button.jsx import React from 'react'; const Button = ({ children, onClick }) => { return ( <button onClick={onClick}> {children} </button> ); }; export default Button;
설정 및 구성
먼저 Vitest와 @testing-library/react
(또는 해당 프레임워크의 동등한 라이브러리)가 설치되어 있는지 확인하십시오.
npm install -D vitest @testing-library/react @testing-library/jest-dom
package.json
에 test
스크립트를 추가하십시오.
"scripts": { "test": "vitest" }
vitest.config.js
를 구성하여 @testing-library/jest-dom
매처에 대한 필요한 설정 파일을 포함하십시오.
// vitest.config.js import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], test: { environment: 'jsdom', setupFiles: './setupTests.js', globals: true, // 'describe', 'it', 'expect'를 전역으로 만듭니다. }, });
setupTests.js
를 만드십시오.
// setupTests.js import '@testing-library/jest-dom';
단위 테스트: 버튼 렌더링
Button
컴포넌트가 제공된 텍스트로 올바르게 렌더링되는지 확인하는 단위 테스트를 작성해 봅시다.
// components/Button.test.jsx import React from 'react'; import { render, screen } from '@testing-library/react'; import Button from './Button'; describe('Button Component', () => { it('should render with correct text', () => { render(<Button>Click Me</Button>); expect(screen.getByText('Click Me')).toBeInTheDocument(); }); it('should render different text', () => { render(<Button>Submit</Button>); expect(screen.getByText('Submit')).toBeInTheDocument(); }); });
이 테스트에서:
render(<Button>Click Me</Button>)
은jsdom
에서 제공하는 가상 DOM에 컴포넌트를 마운트합니다.screen.getByText('Click Me')
는 Testing Library의 쿼리 API를 사용하여 "Click Me"라는 텍스트를 포함하는 요소를 찾습니다. 이것은 사용자가 버튼을 시각적으로 찾는 방식을 시뮬레이션합니다.expect(...).toBeInTheDocument()
는@testing-library/jest-dom
에서 제공하는 단언으로, 요소가 문서에 존재하는지 확인합니다.
상호작용 테스트: 버튼 클릭
다음으로, 버튼을 클릭할 때 onClick
핸들러가 호출되는지 확인하는 상호작용 테스트를 작성해 봅시다.
// components/Button.test.jsx import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { vi } from 'vitest'; // Mocking을 위해 vi를 가져옵니다. import Button from './Button'; describe('Button Component', () => { // ... (이전 테스트) ... it('should call onClick handler when clicked', () => { const handleClick = vi.fn(); // Mock 함수 생성 render(<Button onClick={handleClick}>Click Me</Button>); const buttonElement = screen.getByText('Click Me'); fireEvent.click(buttonElement); // 클릭 이벤트 시뮬레이션 expect(handleClick).toHaveBeenCalledTimes(1); // Mock 함수가 호출되었는지 단언 }); it('should not call onClick if disabled (example, if we add disabled prop later)', () => { // 'disabled' prop이 나중에 추가되는 경우 추가될 테스트입니다. // 지금은 상태로 상호작용 테스트의 개념적 예시 역할을 합니다. const handleClick = vi.fn(); render(<button disabled onClick={handleClick}>Disabled Button</button>); // 단순화를 위해 네이티브 버튼 사용 fireEvent.click(screen.getByText('Disabled Button')); expect(handleClick).not.toHaveBeenCalled(); }); });
여기서:
const handleClick = vi.fn();
은 Vitest Mock 함수를 생성합니다. Mock 함수를 사용하면 함수가 호출되었는지, 몇 번 호출되었는지, 어떤 인수로 호출되었는지 추적할 수 있습니다.fireEvent.click(buttonElement)
은 사용자가 버튼을 클릭하는 것을 시뮬레이션합니다.fireEvent
는 DOM 이벤트를 디스패치하는 Testing Library의 또 다른 유틸리티입니다.expect(handleClick).toHaveBeenCalledTimes(1)
은 Mock 함수가 정확히 한 번 호출되었음을 단언하여onClick
prop이 올바르게 트리거되었음을 확인합니다.
고급 상호작용: 입력 컴포넌트
좀 더 복잡한 Input
컴포넌트를 고려해 봅시다.
// components/Input.jsx import React, { useState } from 'react'; const Input = ({ label }) => { const [value, setValue] = useState(''); return ( <div> <label htmlFor="my-input">{label}</label> <input id="my-input" type="text" value={value} onChange={(e) => setValue(e.target.value)} placeholder="Enter text" /> <p>Current Value: {value}</p> </div> ); }; export default Input;
해당 상호작용 테스트:
// components/Input.test.jsx import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import Input from './Input'; describe('Input Component', () => { it('should update its value when text is typed', () => { render(<Input label="Name" />); const inputElement = screen.getByLabelText('Name'); // 연결된 레이블로 쿼리 expect(inputElement).toHaveValue(''); // 초기 상태 fireEvent.change(inputElement, { target: { value: 'John Doe' } }); // 타이핑 시뮬레이션 expect(inputElement).toHaveValue('John Doe'); // 입력 값 확인 expect(screen.getByText('Current Value: John Doe')).toBeInTheDocument(); // 표시된 값 확인 }); });
이 고급 상호작용 테스트의 주요 사항:
screen.getByLabelText('Name')
은 사용자가 양식과 상호작용하는 방식을 반영하는 입력 요소를 쿼리하는 매우 권장되는 방법입니다.fireEvent.change(...)
는 입력 필드에 타이핑하는 것을 시뮬레이션합니다.onChange
핸들러가 예상하는event.target.value
속성을 모방하기 위해target
을 객체로 전달합니다.- 입력 요소의 값과 현재 값을 표시하는 다른 요소 모두에 대해 단언하여 구성 요소의 상태 관리 및 렌더링이 올바른지 확인합니다.
결론
Vitest의 놀라운 속도와 개발자 경험을 Testing Library의 사용자 중심 철학과 전략적으로 결합하면 강력하고 안정적인 프론트엔드 컴포넌트를 구축할 수 있는 강력한 도구를 갖추게 됩니다. 이러한 테스트는 회귀를 포착할 뿐만 아니라 살아있는 문서 역할을 하여 코드베이스에 대한 자신감을 키우고 우아한 발전을 가능하게 합니다. Vitest와 Testing Library를 채택하여 진정으로 중요한 테스트를 작성하고 컴포넌트를 탄력적으로 유지하며 개발 워크플로우를 더욱 즐겁게 만드십시오.