React Testing Guide
React testing guide for beginners and advanced developers
React Testing Guide
Test yozish — kod sifati va barqarorligini ta’minlash uchun eng muhim bosqich. Bu maqolada sizga unit, integration va end-to-end testlarni qanday tashkil qilish, qaysi vositalardan foydalanish, testlar strukturasini professional darajada yaratish va ularni CI/CD ga integratsiya qilish bo‘yicha to‘liq qadam-boqadam ko‘rsatmalar beramiz.
1. Test Piramidasi va Strategiya
graph LR
A[End-to-End Tests] --> B[Integration Tests]
B --> C[Unit Tests]
- Unit Tests (70-80%): individual funktsiyalar & komponentlar
- Integration Tests (15-20%): komponentlar va servislar o‘zaro aloqasi
- End-to-End Tests (5-10%): foydalanuvchi yo‘li (UI → API → DB)
Nima uchun?
- 🔍 Unit tests tez va izolyatsiya qilingan
- 🔗 Integration tests modulár ishini ta’minlaydi
- 🌐 E2E tests haqiqiy foydalanuvchi jarayonini tekshiradi
2. Muhitni Sozlash
2.1. Jest bilan (Create-React-App yoki Next.js)
npm install --save-dev jest @testing-library/react @testing-library/jest-dom babel-jest
jest.config.js
:
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
moduleNameMapper: {
'\\.(css|less|scss)$': 'identity-obj-proxy',
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
}
jest.setup.js
:
import '@testing-library/jest-dom'
2.2. Vitest bilan (Vite)
npm install --save-dev vitest @testing-library/react @testing-library/jest-dom jsdom
vite.config.ts
:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './vitest.setup.ts',
},
})
vitest.setup.ts
:
import '@testing-library/jest-dom'
3. Unit Testlar
3.1. AAA Pattern (Arrange-Act-Assert)
- Arrange: test uchun zarur holatni yaratish
- Act: funksiya yoki komponentni chaqirish
- Assert: kutgan natijani tekshirish
// Counter.tsx
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => setCount(c => c + 1)}>+</button>
<span data-testid='value'>{count}</span>
</div>
)
}
// Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Counter } from './Counter'
test('Counter + tugmasi bosilganda bittaga oshadi', () => {
// Arrange
render(<Counter />)
const btn = screen.getByText('+')
const value = screen.getByTestId('value')
// Act
fireEvent.click(btn)
// Assert
expect(value).toHaveTextContent('1')
})
3.2. Mocking & Spy
jest.mock()
API chaqiruvlarini soxtalashtirishjest.fn()
callback funktsiyalarni kuzatish
// api.ts
export async function fetchUser(id: string) {
const res = await fetch(`/api/users/${id}`)
return res.json()
}
// api.test.ts
import { fetchUser } from './api'
global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ id: '1', name: 'Ali' }) })
)
test('fetchUser foydalanuvchi maʼlumotini qaytaradi', async () => {
const user = await fetchUser('1')
expect(fetch).toHaveBeenCalledWith('/api/users/1')
expect(user.name).toBe('Ali')
})
4. Integration Testlar
Integration testlar bir nechta komponent yoki modulni birga sinovdan o‘tkazadi.
// LoginForm.tsx
import { useState } from 'react'
import { login } from './services/auth.service'
export function LoginForm() {
const [email, setEmail] = useState('')
const [msg, setMsg] = useState('')
const handleSubmit = async e => {
e.preventDefault()
const res = await login(email)
setMsg(res.success ? 'Xush kelibsiz!' : 'Xato')
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder='Email'
value={email}
onChange={e => setEmail(e.target.value)}
/>
<button type='submit'>Kirish</button>
{msg && <p>{msg}</p>}
</form>
)
}
// LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { LoginForm } from './LoginForm'
import * as auth from './services/auth.service'
jest.spyOn(auth, 'login').mockResolvedValue({ success: true })
test('muvaffaqiyatli login xabarini ko‘rsatadi', async () => {
render(<LoginForm />)
fireEvent.change(screen.getByPlaceholderText('Email'), {
target: { value: 'a@example.com' },
})
fireEvent.click(screen.getByText('Kirish'))
await waitFor(() =>
expect(screen.getByText('Xush kelibsiz!')).toBeInTheDocument()
)
})
5. End-to-End (E2E) Testlar
5.1. Cypress
npm install --save-dev cypress
cypress/integration/login.spec.js
:
describe('Login Flow', () => {
it('foydalanuvchi muvaffaqiyatli kirishi', () => {
cy.visit('/login')
cy.get('input[placeholder="Email"]').type('a@example.com')
cy.get('button').contains('Kirish').click()
cy.contains('Dashboard').should('be.visible')
})
})
5.2. Playwright
npm install --save-dev playwright @playwright/test
tests/login.spec.ts
:
import { test, expect } from '@playwright/test'
test('foydalanuvchi login bo‘lishi', async ({ page }) => {
await page.goto('http://localhost:3000/login')
await page.fill('input[placeholder="Email"]', 'a@example.com')
await page.click('text=Kirish')
await expect(page.locator('text=Dashboard')).toBeVisible()
})
6. CI/CD ga Integratsiya
GitHub Actions misoli
.github/workflows/test.yml
:
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm run test -- --coverage
- name: Upload coverage report
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage
7. Eng Yaxshi Amaliyotlar
- 🛡️ Test isolation: har bir test o‘z holatida bo‘lsin
- 📑 Fast coverage check: < 80% ni pasaytirmang
- 🏷️ Clear test names:
should
,renders
,calls
kabi - ⚖️ Avoid flaky tests:
waitFor
o‘rnigafindBy…
- 🧹 Fast cleanup (Teardown): DOM va mocks’larni qayta tiklash
8. Xulosa
- Unit tests - kod logikasini tekshiradi
- Integration tests - modulár o‘zaro aloqasini sinaydi
- E2E tests - foydalanuvchi tajribasini sinaydi
- CI/CD - har push va PR’da avtomatik test
Test yozish — bu kodga sarmoya. Ustuvorlik berilsin, muntazam qayta ko‘rib chiqilsin va jamoaviy madaniyatga aylanadi.