React Testing Library — Server Component testlash

~20 daq · Next.js · Testing

Muammodan boshlaylik

Savat komponentida "−" tugmasi soni 1 bo'lganda bosilsa, mahsulot o'chishi kerak. Kimdir CSS klass nomini o'zgartirdi — test buzildi, garchi funksiya ishlasa ham. Boshqa safar tugma ishlamay qoldi — lekin test o'tdi, chunki u ichki state ni tekshirgan edi.

Testlar amalga oshirish tafsilotlarini emas, foydalanuvchi ko'radigan va qiladigan narsani tekshirishi kerak.


Hayotiy o'xshatma

Mashinani sinash. Mexanik dvigatel ichidagi har bir vintni tekshirishi mumkin — lekin xaridor uchun muhimi: kalit burilganda yonadimi, rul buriladimi, tormoz ishlaydimi. React Testing Library — xaridor nuqtai nazaridan sinov.


Asosiy tamoyil

"Testlaringiz dasturingiz qanday ishlatilishiga qanchalik o'xshasa, ular sizga shunchalik ko'p ishonch beradi."

  • Elementlarni rol, matn, label bo'yicha topish — klass yoki ID bo'yicha emas
  • Foydalanuvchi harakatlari — userEvent bilan (bosish, yozish)
  • Natijani ekranda tekshirish — state ni emas

Client Component testi

// components/SoniTanlash.tsx
'use client'

import { useState } from 'react'

export function SoniTanlash({ boshlangich = 1, onOchir }: { boshlangich?: number; onOchir: () => void }) {
  const [soni, setSoni] = useState(boshlangich)

  return (
    <div>
      <button aria-label="Kamaytirish" onClick={() => (soni === 1 ? onOchir() : setSoni(soni - 1))}>−</button>
      <span aria-live="polite">{soni}</span>
      <button aria-label="Ko'paytirish" onClick={() => setSoni(soni + 1)}>+</button>
    </div>
  )
}
// components/SoniTanlash.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { SoniTanlash } from './SoniTanlash'

describe('SoniTanlash', () => {
  it('+ bosilganda son oshadi', async () => {
    const user = userEvent.setup()
    render(<SoniTanlash onOchir={() => {}} />)

    await user.click(screen.getByRole('button', { name: "Ko'paytirish" }))

    expect(screen.getByText('2')).toBeInTheDocument()
  })

  it('1 da − bosilsa onOchir chaqiriladi', async () => {
    const user = userEvent.setup()
    const onOchir = vi.fn()
    render(<SoniTanlash onOchir={onOchir} />)

    await user.click(screen.getByRole('button', { name: 'Kamaytirish' }))

    expect(onOchir).toHaveBeenCalledOnce()
  })
})

Elementlarni topish

So'rov Ustuvorlik Misol
getByRole 1 — eng yaxshi getByRole('button', { name: 'Saqlash' })
getByLabelText Formalar uchun getByLabelText('Email')
getByPlaceholderText Label yo'q bo'lsa —
getByText Matn getByText(/120 000 so'm/)
getByTestId Oxirgi chora data-testid="savat-jami"
Variant Topilmasa Qachon
getBy... Xato Element bo'lishi kerak
queryBy... null Element yo'qligini tekshirish
findBy... Promise, kutadi Asinxron paydo bo'ladigan element
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
expect(await screen.findByText('Saqlandi')).toBeInTheDocument()

[!TIP] getByRole dan foydalanish — accessibility uchun ham bepul tekshiruv: agar tugmani rol va nomi bilan topa olmasangiz, ekran o'quvchi foydalanuvchi ham uni tushunmaydi.


Forma testi

it("noto'g'ri email da xato ko'rsatadi", async () => {
  const user = userEvent.setup()
  render(<RoyxatForma />)

  await user.type(screen.getByLabelText('Email'), 'lola')
  await user.click(screen.getByRole('button', { name: "Ro'yxatdan o'tish" }))

  expect(await screen.findByRole('alert')).toHaveTextContent("Email noto'g'ri")
})

Server Component lar

Sinxron Server Component — oddiy render

// components/MahsulotKarta.tsx (Server Component, async emas)
export function MahsulotKarta({ nomi, narx }: { nomi: string; narx: number }) {
  return (
    <article>
      <h3>{nomi}</h3>
      <p>{narx.toLocaleString('uz-UZ')} so'm</p>
    </article>
  )
}
it("nom va narxni ko'rsatadi", () => {
  render(<MahsulotKarta nomi="Choynak" narx={120000} />)
  expect(screen.getByRole('heading', { name: 'Choynak' })).toBeInTheDocument()
})

Async bo'lmagan Server Component — oddiy funksiya, RTL uni muammosiz render qiladi.

Async Server Component

export default async function Katalog() {
  const mahsulotlar = await getMahsulotlar()
  return <Royxat mahsulotlar={mahsulotlar} />
}

[!WARNING] render(<Katalog />) — async komponentlar React Testing Library va Vitest da hozircha to'liq qo'llab-quvvatlanmaydi. Tavsiya etilgan yondashuv — ajratish:

  • Ma'lumot olish (getMahsulotlar) — unit test, bazani mock qilib
  • Ko'rsatish (Royxat — sinxron, prop oladi) — RTL bilan
  • Ikkalasi birga, haqiqiy sahifa — Playwright (63-dars)

Kichik yo'l (ehtiyotkorlik bilan): async komponentni funksiya sifatida chaqirib, natijani render qilish:

vi.mock('@/lib/data', () => ({ getMahsulotlar: async () => [{ id: '1', nomi: 'Choynak', narx: 120000 }] }))

it('katalog', async () => {
  render(await Katalog())
  expect(screen.getByText('Choynak')).toBeInTheDocument()
})

Bu faqat ichida boshqa async komponentlar va server API lari (cookies()) bo'lmagan oddiy holatlarda ishlaydi.


Server Action ni test qilish

Server Action — oddiy async funksiya:

vi.mock('@/lib/dal', () => ({ talabQil: async () => ({ id: 'u1' }) }))
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
vi.mock('next/navigation', () => ({ redirect: vi.fn() }))

it("qisqa sarlavha — xato qaytaradi", async () => {
  const fd = new FormData()
  fd.set('sarlavha', 'ab')
  fd.set('matn', 'yetarlicha uzun matn')

  const natija = await postYarat(undefined, fd)
  expect(natija?.xatolar?.sarlavha).toBeDefined()
})

Tez-tez uchraydigan xatolar

1. fireEvent o'rniga userEvent ishlatmaslik

fireEvent.click — faqat bitta hodisa; userEvent — haqiqiy foydalanuvchi kabi (focus, mousedown, mouseup, click).

2. await siz userEvent

user.click(...) — Promise; await siz keyingi tekshiruv erta ishlaydi.

3. Klass bo'yicha qidirish

container.querySelector('.btn-primary') — dizayn o'zgarsa test buziladi.


Xulosa

RTL — foydalanuvchi nuqtai nazaridan: rol, label, matn
getByRole > getByLabelText > getByText > getByTestId
getBy (bo'lishi kerak), queryBy (yo'qligi), findBy (asinxron)
userEvent.setup() + await user.click / user.type
Sinxron Server Component — oddiy render
Async Server Component — mantiqni ajrating; to'liq — Playwright
Server Action — oddiy funksiya, next/* modullarini mock qiling

Keyingi dars

63-dars: Playwright — End-to-End testing, brauzerda test

Haqiqiy brauzerda butun foydalanuvchi oqimini avtomatik sinash.