# Testowanie React w 2026: Vitest, React Testing Library i najlepsze praktyki > Kompleksowy przewodnik po testowaniu aplikacji React z użyciem Vitest i React Testing Library. Wzorce testowania komponentów, obsługa operacji asynchronicznych, strategie mockowania oraz praktyki przydatne na rozmowach rekrutacyjnych. - Published: 2026-07-09 - Updated: 2026-07-09 - Author: SharpSkill - Tags: react, testing, vitest, react-testing-library - Reading time: 9 min --- Testowanie aplikacji React w 2026 roku koncentruje się na [Vitest](https://vitest.dev/) jako dominującym narzędziu do uruchamiania testów oraz [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) (RTL) jako standardzie testowania komponentów. Ta kombinacja zapewnia szybkie pętle informacji zwrotnej, natywne wsparcie dla modułów ES oraz testy weryfikujące zachowanie zamiast szczegółów implementacji. > **Filozofia testowania** > > Testy powinny odzwierciedlać sposób, w jaki użytkownicy wchodzą w interakcję z komponentami. Zapytania powinny opierać się na dostępnych rolach, etykietach i tekście — nie na klasach CSS czy atrybutach data-testid. Takie podejście wykrywa prawdziwe błędy i jest odporne na refaktoryzację. ## Konfiguracja Vitest dla projektów React Vitest zastąpił Jest jako preferowany runner testów dla nowoczesnych aplikacji React. Natywne wsparcie dla modułów ES eliminuje narzut transformacji, a ścisła integracja z Vite oznacza, że konfiguracja odzwierciedla środowisko deweloperskie. Produkcyjna konfiguracja Vitest obsługuje transformację JSX, aliasy ścieżek i konfigurację środowiska testowego bez zbędnego boilerplate: ```typescript // vitest.config.ts 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/**'], }, }, }) ``` Plik setup rozszerza matchery i konfiguruje czyszczenie między testami: ```typescript // src/test/setup.ts 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(), })), }) ``` Ta konfiguracja zapewnia czysty stan dla każdego testu oraz obsługuje typowe mocki API przeglądarki, których brakuje w jsdom. ## Testowanie komponentów z React Testing Library React Testing Library wymusza testowanie komponentów z perspektywy użytkownika. Zamiast inspekcji wewnętrznego stanu czy wartości propsów, testy wchodzą w interakcję z renderowanym wyjściem poprzez zapytania dostępności. Przykład komponentu wyszukiwania filtrującego wyniki i wyświetlającego stan ładowania: ```tsx // SearchResults.tsx import { useState } from 'react' interface SearchResultsProps { onSearch: (query: string) => Promise } export function SearchResults({ onSearch }: SearchResultsProps) { const [query, setQuery] = useState('') const [results, setResults] = useState([]) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(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 (
setQuery(e.target.value)} /> {error &&

{error}

}
    {results.map((result, i) => (
  • {result}
  • ))}
) } ``` Testy weryfikują zachowanie komponentu podczas interakcji użytkownika i stanów asynchronicznych: ```typescript // SearchResults.test.tsx 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() // 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((resolve) => { resolveSearch = resolve }) const mockSearch = vi.fn().mockReturnValue(searchPromise) const user = userEvent.setup() render() 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() 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.' ) }) }) ``` Biblioteka `userEvent` symuluje realistyczne interakcje użytkownika, włącznie ze zdarzeniami klawiatury i zarządzaniem focusem, wykrywając błędy pomijane przez zdarzenia syntetyczne. ## Wzorce testowania asynchronicznego i waitFor Operacje asynchroniczne wymagają jawnych strategii oczekiwania. React Testing Library dostarcza zapytania `findBy`, które łączą `getBy` z automatycznym oczekiwaniem, oraz `waitFor` dla złożonych asercji. > **Priorytet zapytań** > > Preferuj `findBy` nad `waitFor` + `getBy` podczas oczekiwania na pojawienie się elementów. Rezerwuj `waitFor` dla asercji na istniejących elementach, które zmieniają stan. Typowe wzorce asynchroniczne pojawiają się podczas testowania hooków i komponentów pobierających dane: ```typescript // async-patterns.test.tsx 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() // 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() // 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() 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() // queryBy returns null instead of throwing, use for absence checks expect(screen.queryByText('Premium')).not.toBeInTheDocument() }) ``` Dostosowanie timeout pomaga przy wolniejszych operacjach bez spowalniania całego zestawu testów: ```typescript // Increase timeout for slow operations await screen.findByText('Upload complete', {}, { timeout: 5000 }) await waitFor( () => expect(mockSubmit).toHaveBeenCalled(), { timeout: 3000, interval: 100 } ) ``` ## Strategie mockowania: moduły, API i hooki Możliwości mockowania Vitest obsługują zewnętrzne zależności bez zanieczyszczania grafu modułów. Strategiczne mockowanie izoluje komponenty od wywołań sieciowych, bibliotek zewnętrznych i złożonych zależności. Mockowanie modułów zastępuje importy na poziomie bundlera: ```typescript // api.test.ts 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', }) }) }) ``` Dla [hooków React zależnych od kontekstu lub stanu zewnętrznego](https://testing-library.com/docs/react-testing-library/api/#renderhook), narzędzie `renderHook` umożliwia izolowane testowanie: ```typescript // useAuth.test.ts 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 }) => ( {children} ), }) 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) zapewnia mockowanie API na poziomie sieci, umożliwiając testy wykonujące rzeczywiste wywołania fetch: ```typescript // handlers.ts 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 } ) }), ] ``` ## Testowanie Server Components w React 19 Server Components stanowią unikalne wyzwanie testowe, ponieważ wykonują się na serwerze i strumieniują HTML do klienta. Bezpośrednie testy jednostkowe wymagają innego podejścia niż testowanie komponentów klienckich. Dla Server Components wykonujących pobieranie danych, należy testować wyjście renderowania z mockami źródeł danych: ```typescript // ServerComponent.test.tsx 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') }) }) ``` Testy integracyjne z [Playwright](https://playwright.dev/) zapewniają najbardziej niezawodne pokrycie dla Server Components, testując pełny pipeline renderowania: ```typescript // e2e/server-component.spec.ts 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() }) ``` ## Testowanie custom hooków ze złożonym stanem Custom hooki zarządzające złożonym stanem, efektami ubocznymi lub zewnętrznymi subskrypcjami korzystają z dedykowanych testów. Narzędzie `renderHook` z React Testing Library obsługuje cykl życia hooka i aktualizacje. Przykład hooka debounced search koordynującego się z API: ```typescript // useDebounceSearch.ts import { useState, useEffect, useCallback } from 'react' export function useDebounceSearch( searchFn: (query: string) => Promise, delay = 300 ) { const [query, setQuery] = useState('') const [results, setResults] = useState([]) 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 } } ``` Testy weryfikują zachowanie debouncingu i przejścia stanów: ```typescript // useDebounceSearch.test.ts 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']) }) }) ``` Fałszywe timery z Vitest umożliwiają deterministyczne testowanie zachowań zależnych od czasu bez rzeczywistych opóźnień. ## Pytania rekrutacyjne dotyczące testowania React Rozmowy techniczne często badają wiedzę o testowaniu. Te pytania oceniają praktyczne zrozumienie, a nie koncepcje teoretyczne. > **Częsty błąd** > > Należy unikać testowania szczegółów implementacji, takich jak wartości stanu czy metody komponentów. Testy, które psują się podczas refaktoryzacji wewnętrznej struktury, dają fałszywe poczucie pewności i obciążenie związane z utrzymaniem. **Priorytet zapytań w React Testing Library** Zalecany priorytet zapytań kieruje się dostępnością: `getByRole` > `getByLabelText` > `getByPlaceholderText` > `getByText` > `getByTestId`. Zapytania oparte na rolach zapewniają dostępność komponentów, a testy przetrwają zmiany w strukturze znaczników. **Kiedy używać `findBy` vs `waitFor`** Zapytania `findBy` czekają na pojawienie się elementów w DOM i zwracają obietnicę. Należy ich używać, oczekując nowych elementów po operacjach asynchronicznych. `waitFor` opakowuje asercje, które mogą nie przejść natychmiast, ponawiając próby aż do sukcesu lub timeout. Należy używać `waitFor` podczas asercji na zmianach stanu istniejących elementów. **Mockowanie fetch vs MSW** Bezpośrednie mockowanie fetch (`vi.spyOn(global, 'fetch')`) działa dla prostych przypadków, ale wymaga reimplementacji obiektów Response i obsługi błędów. MSW przechwytuje na poziomie sieci, pozwalając testom wykonywać rzeczywiste wywołania fetch, serializację żądań i ścieżki obsługi błędów. Dla głębszej praktyki z tymi koncepcjami, warto zapoznać się z [pytaniami rekrutacyjnymi React Testing](/technologies/react-next/interview-questions/react-testing) na SharpSkill. ## Podsumowanie - Konfiguracja Vitest ze środowiskiem jsdom, plikami setup dla rozszerzeń matcherów i raportowaniem pokrycia dla widoczności luk w pokryciu testowym - Zapytania o komponenty przez dostępne role i etykiety pozwalają pisać testy weryfikujące zachowanie widoczne dla użytkownika i wykrywające prawdziwe błędy - Używanie zapytań `findBy` dla elementów pojawiających się po operacjach asynchronicznych, `waitFor` dla asercji na zmieniającym się stanie - Mockowanie na odpowiednim poziomie: mocki modułów dla izolacji jednostkowej, MSW dla realistycznego testowania warstwy sieciowej - Testowanie custom hooków z `renderHook` i fałszywymi timerami w celu weryfikacji złożonego zarządzania stanem i efektów ubocznych - Server Components wymagają albo asynchronicznego renderowania w testach, albo pełnych testów integracyjnych z Playwright - Powiązana lektura: [Zaawansowane wzorce React Hooks](/blog/react-next/advanced-react-hooks-patterns-optimizations) i [TypeScript z React](/technologies/react-next/interview-questions/typescript-react) dla wzorców testowania z typami --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/react-next/react-testing-2026-vitest-rtl-best-practices