폼 제출 시 FormData 테스트
test('폼 제출 시 formData 확인', () => {
const handleSubmit = jest.fn((e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.target as HTMLFormElement)
const data = Object.fromEntries(formData.entries())
expect(data).toEqual({ username: 'testuser', password: 'password' })
})
render(<LoginForm onSubmit={handleSubmit} />)
fireEvent.change(screen.getByPlaceholderText('아이디'), {
target: { value: 'testuser' },
})
fireEvent.change(screen.getByPlaceholderText('비밀번호'), {
target: { value: 'password' },
})
fireEvent.click(screen.getByRole('button', { name: '로그인' }))
expect(handleSubmit).toHaveBeenCalled()
}) AuthContext 테스트 패턴
// localStorage 함수 테스트
vi.spyOn(Storage.prototype, 'getItem').mockImplementation((key) => {
if (key === 'auth_token') return 'test_token'
return null
})
// AuthProvider 상태 테스트
const TestComponent = () => {
const { token, login, logout } = useAuth()
return (
<div>
<p data-testid="token">{token || 'null'}</p>
<button onClick={() => login('test_token')}>Login</button>
<button onClick={logout}>Logout</button>
</div>
)
}
// useAuth 훅 테스트
it('AuthProvider 외부에서 호출하면 에러', () => {
expect(() => renderHook(() => useAuth())).toThrowError()
})
it('AuthProvider 내부에서 정상 동작', () => {
const wrapper = ({ children }) => <AuthProvider>{children}</AuthProvider>
const { result } = renderHook(() => useAuth(), { wrapper })
expect(result.current).toHaveProperty('token', null)
}) Vitest/Jest Fake Timers로 시간 제어
describe('time-dependent tests', () => {
beforeEach(() => {
vi.useFakeTimers() // jest.useFakeTimers('modern')
})
afterEach(() => {
vi.useRealTimers() // jest.useRealTimers()
})
it('특정 시간에 동작 확인', () => {
vi.setSystemTime(new Date(2000, 1, 1, 13)) // 13시 설정
expect(purchase()).toEqual({ message: 'Success' })
})
it('영업시간 외 동작', () => {
vi.setSystemTime(new Date(2000, 1, 1, 19)) // 19시 설정
expect(purchase()).toEqual({ message: 'Error' })
})
})
@sinonjs/fake-timers 기반. Date, setTimeout 등 모킹.
Jest setupFiles vs setupFilesAfterEnv - 실행 시점이 다르다.
- setupFiles: 테스트 프레임워크 설치 전. Jest 전역 객체 없음. 환경 변수, 폴리필용.
- setupFilesAfterEnv: 테스트 프레임워크 설치 후.
jest.setTimeout(), 커스텀 matcher, 전역beforeEach용.
process.env는 setupFiles에서. 모듈이 import 시점에 환경 변수를 읽기 때문에 setupFilesAfterEnv에서 설정하면 이미 늦다.
// jest.config.js
{ setupFiles: ['./jest.env.js'], setupFilesAfterEnv: ['./jest.setup.js'] }
// jest.env.js - 환경 변수
process.env.API_URL = 'http://test-api.example.com'
// jest.setup.js - Jest API 활용
import '@testing-library/jest-dom'
beforeEach(() => { jest.clearAllMocks() }) Vitest 모킹: vi.mocked() vs vi.hoisted()
vi.mocked()는 타입만 제공, 실제 모킹 구현체는 별도로 필요.
// ❌ mockImplementation이 undefined
const mockUseSize = vi.mocked(useSize)
// ✅ vi.hoisted() 사용 (추천)
const mockUseSize = vi.hoisted(() => vi.fn())
vi.mock('ahooks', () => ({ useSize: mockUseSize }))
beforeEach(() => {
mockUseSize.mockImplementation(() => ({ width: 100, height: 20 }))
})
DOM 속성 모킹 (scrollWidth/clientWidth):
beforeEach(() => {
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', {
configurable: true,
get() {
return 150
},
})
})
afterEach(() => {
// 원래 속성 복원
})
DOM 측정이 복잡하면 Playwright/Cypress로 통합 테스트 고려.
RTL에서 overflow scroll 상태 테스트
// scrollWidth > clientWidth면 가로 스크롤 필요
const isOverflowScrollable = (el) => ({
horizontal: el.scrollWidth > el.clientWidth,
vertical: el.scrollHeight > el.clientHeight,
})
test('overflow 확인', () => {
render(<ScrollableComponent parentWidth={300} childWidth={500} />)
const parent = screen.getByTestId('parent-container')
expect(isOverflowScrollable(parent).horizontal).toBe(true)
})
test('동적 크기 변경', () => {
const { rerender } = render(<Comp parentWidth={400} childWidth={300} />)
expect(isOverflowScrollable(screen.getByTestId('parent')).horizontal).toBe(
false
)
rerender(<Comp parentWidth={400} childWidth={600} />)
expect(isOverflowScrollable(screen.getByTestId('parent')).horizontal).toBe(
true
)
})
CAUTION
DOM 완전 렌더링 후 측정해야 정확. getBoundingClientRect()는 실제 크기 반환.