2026'da React Testleri: Vitest, React Testing Library ve En İyi Uygulamalar
Vitest ve React Testing Library ile React test yazımında uzmanlaşın. Bileşen test kalıpları, asenkron işlem yönetimi, mock stratejileri ve mülakat için hazır en iyi uygulamalar.

2026 yılında React testi, baskın test çalıştırıcısı olarak Vitest ve bileşen testi standardı olarak React Testing Library (RTL) üzerine odaklanmaktadır. Bu kombinasyon hızlı geri bildirim döngüleri, yerel ES modül desteği ve uygulama detayları yerine davranışı doğrulayan testler sunar.
Kullanıcıların bileşenlerle nasıl etkileşime girdiğini yansıtan testler yazılmalıdır. Erişilebilir roller, etiketler ve metin üzerinden sorgulama yapılmalı — CSS sınıfları veya data-testid nitelikleri üzerinden değil. Bu yaklaşım gerçek hataları yakalar ve refaktörizasyona dayanıklıdır.
React Projeleri için Vitest Yapılandırması
Vitest, modern React uygulamaları için tercih edilen test çalıştırıcısı olarak Jest'in yerini aldı. Yerel ES modül desteği dönüşüm yükünü ortadan kaldırır ve Vite ile sıkı entegrasyon, yapılandırmanın geliştirme ortamını yansıtması anlamına gelir.
Üretime hazır bir Vitest yapılandırması, JSX dönüşümü, yol takma adları ve test ortamı kurulumunu gereksiz şablon kod olmadan yönetir:
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
// Use jsdom for DOM APIs and React rendering
environment: 'jsdom',
// Run setup file before each test file
setupFiles: ['./src/test/setup.ts'],
// Include only test files, exclude e2e
include: ['src/**/*.{test,spec}.{ts,tsx}'],
// Enable global test APIs (describe, it, expect)
globals: true,
// Generate coverage reports
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: ['node_modules', 'src/test/**'],
},
},
})Kurulum dosyası eşleştiricileri genişletir ve testler arasında temizlik yapılandırır:
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach, vi } from 'vitest'
// Clean up rendered components after each test
afterEach(() => {
cleanup()
})
// Mock window.matchMedia for responsive components
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})Bu kurulum her test için temiz bir durum sağlar ve jsdom'da bulunmayan yaygın tarayıcı API mock'larını yönetir.
React Testing Library ile Bileşen Testi
React Testing Library, bileşenlerin kullanıcı perspektifinden test edilmesini zorunlu kılar. İç durumu veya prop değerlerini incelemek yerine, testler erişilebilirlik sorguları aracılığıyla render edilmiş çıktıyla etkileşime girer.
Sonuçları filtreleyen ve yükleme durumlarını gösteren bir arama bileşeni örneği:
import { useState } from 'react'
interface SearchResultsProps {
onSearch: (query: string) => Promise<string[]>
}
export function SearchResults({ onSearch }: SearchResultsProps) {
const [query, setQuery] = useState('')
const [results, setResults] = useState<string[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
async function handleSearch() {
if (!query.trim()) return
setIsLoading(true)
setError(null)
try {
const data = await onSearch(query)
setResults(data)
} catch (e) {
setError('Search failed. Please try again.')
} finally {
setIsLoading(false)
}
}
return (
<div>
<label htmlFor="search-input">Search</label>
<input
id="search-input"
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button onClick={handleSearch} disabled={isLoading}>
{isLoading ? 'Searching...' : 'Search'}
</button>
{error && <p role="alert">{error}</p>}
<ul aria-label="Search results">
{results.map((result, i) => (
<li key={i}>{result}</li>
))}
</ul>
</div>
)
}Testler, kullanıcı etkileşimleri ve asenkron durumlar boyunca bileşenin davranışını doğrular:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import { SearchResults } from './SearchResults'
describe('SearchResults', () => {
it('displays results after successful search', async () => {
// Arrange: mock the search function
const mockSearch = vi.fn().mockResolvedValue(['React', 'Vue', 'Angular'])
const user = userEvent.setup()
render(<SearchResults onSearch={mockSearch} />)
// Act: type a query and click search
await user.type(screen.getByLabelText('Search'), 'framework')
await user.click(screen.getByRole('button', { name: 'Search' }))
// Assert: results appear in the list
expect(await screen.findByText('React')).toBeInTheDocument()
expect(screen.getByText('Vue')).toBeInTheDocument()
expect(mockSearch).toHaveBeenCalledWith('framework')
})
it('shows loading state during search', async () => {
// Create a promise that doesn't resolve immediately
let resolveSearch: (value: string[]) => void
const searchPromise = new Promise<string[]>((resolve) => {
resolveSearch = resolve
})
const mockSearch = vi.fn().mockReturnValue(searchPromise)
const user = userEvent.setup()
render(<SearchResults onSearch={mockSearch} />)
await user.type(screen.getByLabelText('Search'), 'test')
await user.click(screen.getByRole('button', { name: 'Search' }))
// Button shows loading state
expect(screen.getByRole('button', { name: 'Searching...' })).toBeDisabled()
// Resolve the search to clean up
resolveSearch!([])
})
it('displays error message on search failure', async () => {
const mockSearch = vi.fn().mockRejectedValue(new Error('Network error'))
const user = userEvent.setup()
render(<SearchResults onSearch={mockSearch} />)
await user.type(screen.getByLabelText('Search'), 'query')
await user.click(screen.getByRole('button', { name: 'Search' }))
// Error message appears with alert role for screen readers
expect(await screen.findByRole('alert')).toHaveTextContent(
'Search failed. Please try again.'
)
})
})userEvent kütüphanesi, klavye olayları ve odak yönetimi dahil gerçekçi kullanıcı etkileşimlerini simüle eder ve sentetik olayların kaçırdığı hataları yakalar.
Asenkron Test Kalıpları ve waitFor
Asenkron işlemler açık bekleme stratejileri gerektirir. React Testing Library, getBy ile otomatik beklemeyi birleştiren findBy sorgularını ve karmaşık doğrulamalar için waitFor sunar.
Elementlerin görünmesini beklerken waitFor + getBy yerine findBy tercih edilmelidir. waitFor, durum değiştiren mevcut elementler üzerindeki doğrulamalar için ayrılmalıdır.
Veri çekme hook'ları ve bileşenlerini test ederken yaygın asenkron kalıplar ortaya çıkar:
import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
// Wait for element to appear (preferred for new elements)
it('loads user profile on mount', async () => {
render(<UserProfile userId="123" />)
// findBy returns a promise, automatically waits up to 1000ms
const heading = await screen.findByRole('heading', { name: /john doe/i })
expect(heading).toBeInTheDocument()
})
// Wait for element to disappear
it('removes loading spinner after data loads', async () => {
render(<DataTable />)
// Wait for spinner to be removed from DOM
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'))
expect(screen.getByRole('table')).toBeInTheDocument()
})
// Wait for multiple conditions
it('enables submit button when form is valid', async () => {
const user = userEvent.setup()
render(<RegistrationForm />)
const submitButton = screen.getByRole('button', { name: 'Submit' })
expect(submitButton).toBeDisabled()
await user.type(screen.getByLabelText('Email'), 'user@example.com')
await user.type(screen.getByLabelText('Password'), 'SecurePass123!')
// waitFor retries the assertion until it passes or times out
await waitFor(() => {
expect(submitButton).toBeEnabled()
})
})
// Avoid false positives with queryBy for absence checks
it('does not show premium badge for free users', async () => {
render(<UserCard subscription="free" />)
// queryBy returns null instead of throwing, use for absence checks
expect(screen.queryByText('Premium')).not.toBeInTheDocument()
})Zaman aşımı özelleştirmesi, tüm test paketini yavaşlatmadan daha yavaş işlemlere yardımcı olur:
// Increase timeout for slow operations
await screen.findByText('Upload complete', {}, { timeout: 5000 })
await waitFor(
() => expect(mockSubmit).toHaveBeenCalled(),
{ timeout: 3000, interval: 100 }
)React / Next.js mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Mock Stratejileri: Modüller, API'ler ve Hook'lar
Vitest'in mock yetenekleri, modül grafiğini kirletmeden harici bağımlılıkları yönetir. Stratejik mock'lama, bileşenleri ağ çağrılarından, üçüncü taraf kütüphanelerden ve karmaşık bağımlılıklardan izole eder.
Modül mock'lama, import'ları paketleyici düzeyinde değiştirir:
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { fetchUserData } from './api'
// Mock the entire fetch module
vi.mock('./http-client', () => ({
httpClient: {
get: vi.fn(),
},
}))
import { httpClient } from './http-client'
describe('fetchUserData', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('transforms API response into user model', async () => {
// Type-safe mock implementation
vi.mocked(httpClient.get).mockResolvedValue({
data: { id: 1, first_name: 'John', last_name: 'Doe' },
})
const user = await fetchUserData(1)
expect(user).toEqual({
id: 1,
fullName: 'John Doe',
})
})
})Bağlama veya harici duruma bağlı React hook'ları için renderHook yardımcı programı izole test etmeyi sağlar:
import { renderHook, waitFor } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { useAuth } from './useAuth'
import { AuthProvider } from './AuthContext'
describe('useAuth', () => {
it('returns authenticated user after login', async () => {
const mockLogin = vi.fn().mockResolvedValue({ id: 1, name: 'Alice' })
const { result } = renderHook(() => useAuth(), {
wrapper: ({ children }) => (
<AuthProvider loginFn={mockLogin}>{children}</AuthProvider>
),
})
expect(result.current.user).toBeNull()
expect(result.current.isAuthenticated).toBe(false)
await result.current.login('alice@example.com', 'password')
await waitFor(() => {
expect(result.current.user).toEqual({ id: 1, name: 'Alice' })
expect(result.current.isAuthenticated).toBe(true)
})
})
})MSW (Mock Service Worker), ağ düzeyinde API mock'laması sağlar ve gerçek fetch çağrılarını kullanan testleri mümkün kılar:
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/users/:id', ({ params }) => {
return HttpResponse.json({
id: params.id,
name: 'Test User',
})
}),
http.post('/api/login', async ({ request }) => {
const body = await request.json()
if (body.email === 'valid@example.com') {
return HttpResponse.json({ token: 'abc123' })
}
return HttpResponse.json(
{ error: 'Invalid credentials' },
{ status: 401 }
)
}),
]React 19 Server Components Testi
Server Components, sunucuda çalıştığı ve HTML'i istemciye aktardığı için benzersiz test zorlukları sunar. Doğrudan birim testi, istemci bileşen testinden farklı bir yaklaşım gerektirir.
Veri çekme işlemi yapan Server Components için, mock'lanmış veri kaynaklarıyla render çıktısını test etmek gerekir:
import { describe, it, expect, vi } from 'vitest'
// Mock the data fetching function
vi.mock('./db', () => ({
getUser: vi.fn().mockResolvedValue({ id: 1, name: 'Server User' }),
}))
import { render, screen } from '@testing-library/react'
import { UserProfile } from './UserProfile.server'
describe('UserProfile Server Component', () => {
it('renders user data from database', async () => {
// Server components are async, await the component
const Component = await UserProfile({ userId: '1' })
render(Component)
expect(screen.getByRole('heading')).toHaveTextContent('Server User')
})
})Playwright ile entegrasyon testleri, tam render pipeline'ını test ederek Server Components için en güvenilir kapsamı sağlar:
import { test, expect } from '@playwright/test'
test('server component renders with streamed data', async ({ page }) => {
await page.goto('/users/1')
// Wait for streaming to complete
await expect(page.getByRole('heading', { name: /profile/i })).toBeVisible()
await expect(page.getByText('Server User')).toBeVisible()
})Karmaşık Durumlu Özel Hook'ların Testi
Karmaşık durum, yan etkiler veya harici abonelikler yöneten özel hook'lar, ayrılmış testlerden fayda sağlar. React Testing Library'den renderHook yardımcı programı, hook yaşam döngüsünü ve güncellemeleri yönetir.
Bir API ile koordine olan debounced arama hook'u örneği:
import { useState, useEffect, useCallback } from 'react'
export function useDebounceSearch<T>(
searchFn: (query: string) => Promise<T[]>,
delay = 300
) {
const [query, setQuery] = useState('')
const [results, setResults] = useState<T[]>([])
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
if (!query) {
setResults([])
return
}
setIsLoading(true)
const timeoutId = setTimeout(async () => {
try {
const data = await searchFn(query)
setResults(data)
} finally {
setIsLoading(false)
}
}, delay)
return () => clearTimeout(timeoutId)
}, [query, searchFn, delay])
return { query, setQuery, results, isLoading }
}Testler debounce davranışını ve durum geçişlerini doğrular:
import { renderHook, act, waitFor } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { useDebounceSearch } from './useDebounceSearch'
describe('useDebounceSearch', () => {
beforeEach(() => {
vi.useFakeTimers()
})
it('debounces search calls', async () => {
const mockSearch = vi.fn().mockResolvedValue(['result1', 'result2'])
const { result } = renderHook(() => useDebounceSearch(mockSearch, 300))
// Type multiple characters quickly
act(() => result.current.setQuery('r'))
act(() => result.current.setQuery('re'))
act(() => result.current.setQuery('rea'))
act(() => result.current.setQuery('reac'))
act(() => result.current.setQuery('react'))
// Search not called yet (debouncing)
expect(mockSearch).not.toHaveBeenCalled()
// Advance timers past debounce delay
await act(async () => {
vi.advanceTimersByTime(300)
})
// Only one search call with final query
expect(mockSearch).toHaveBeenCalledTimes(1)
expect(mockSearch).toHaveBeenCalledWith('react')
})
it('shows loading state during search', async () => {
let resolveSearch: (value: string[]) => void
const mockSearch = vi.fn().mockImplementation(
() => new Promise((resolve) => { resolveSearch = resolve })
)
const { result } = renderHook(() => useDebounceSearch(mockSearch, 100))
act(() => result.current.setQuery('test'))
await act(async () => {
vi.advanceTimersByTime(100)
})
expect(result.current.isLoading).toBe(true)
await act(async () => {
resolveSearch(['result'])
})
expect(result.current.isLoading).toBe(false)
expect(result.current.results).toEqual(['result'])
})
})Vitest'ten sahte zamanlayıcılar, gerçek gecikmeler olmadan zamana bağlı davranışların deterministik testini sağlar.
React Testi Hakkında Mülakat Soruları
Teknik mülakatlar sıklıkla test bilgisini sorgular. Bu sorular teorik kavramlar yerine pratik anlayışı değerlendirir.
Durum değerleri veya bileşen yöntemleri gibi uygulama detaylarını test etmekten kaçınılmalıdır. İç yapı refaktörize edildiğinde bozulan testler yanlış güven ve bakım yükü sağlar.
React Testing Library'de sorgu önceliği
Önerilen sorgu önceliği erişilebilirliği takip eder: getByRole > getByLabelText > getByPlaceholderText > getByText > getByTestId. Rol tabanlı sorgular bileşenlerin erişilebilir kalmasını sağlar ve testler işaretleme değişikliklerine dayanır.
findBy vs waitFor ne zaman kullanılmalı
findBy sorguları DOM'da elementlerin görünmesini bekler ve bir promise döndürür. Asenkron işlemlerden sonra yeni elementler beklenirken kullanılmalıdır. waitFor hemen geçemeyebilecek doğrulamaları sarar, başarı veya zaman aşımına kadar yeniden dener. Mevcut elementlerdeki durum değişikliklerini doğrularken waitFor kullanılmalıdır.
Fetch mock'lama vs MSW
Doğrudan fetch mock'lama (vi.spyOn(global, 'fetch')) basit durumlar için çalışır ancak Response nesnelerinin ve hata yönetiminin yeniden uygulanmasını gerektirir. MSW ağ düzeyinde yakalar, testlerin gerçek fetch çağrılarını, istek serileştirmesini ve hata yönetimi kod yollarını kullanmasına izin verir.
Bu kavramlarla daha derin pratik için SharpSkill'deki React Testing mülakat sorularını inceleyebilirsiniz.
Sonuç
- Vitest'i jsdom ortamı, eşleştirici uzantıları için kurulum dosyaları ve test kapsam boşluklarında görünürlük için kapsam raporlaması ile yapılandırın
- Bileşenleri erişilebilir roller ve etiketlerle sorgulayarak kullanıcıya yönelik davranışı doğrulayan ve gerçek hataları yakalayan testler yazın
- Asenkron işlemlerden sonra görünen elementler için
findBysorgularını, değişen durum üzerindeki doğrulamalar içinwaitForkullanın - Uygun düzeyde mock'layın: birim izolasyonu için modül mock'ları, gerçekçi ağ katmanı testi için MSW
- Karmaşık durum yönetimini ve yan etkileri doğrulamak için
renderHookve sahte zamanlayıcılarla özel hook'ları test edin - Server Components, testlerde asenkron render veya Playwright ile tam entegrasyon testleri gerektirir
- İlgili okuma: Tip güvenli test kalıpları için Gelişmiş React Hooks kalıpları ve React ile TypeScript
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

Next.js 16'da Cache Components: use cache, PPR ve 2026 Mülakat Soruları
Next.js 16 Cache Components rehberi: use cache yönergesi, Partial Pre-Rendering, cacheLife, cacheTag, use cache private ile güvenlik ve kıdemli geliştirici mülakat soruları.

2026'da React Compiler: Otomatik Memoizasyon ve Teknik Mülakat Soruları
React Compiler hakkında kapsamlı rehber — otomatik memoizasyon, derleme hattı, React kuralları, ESLint entegrasyonu ve 2026 yılı React teknik mülakat soruları.

Next.js 16 Server Actions 2026: Mutation, Revalidation ve Mülakat Soruları
Next.js 16 Server Actions'ın mutation, revalidation, bekleme durumu, optimistic UI ve güvenliği nasıl ele aldığı ve her kavramı sınayan mülakat soruları.