06 / 12
Kullanıcı Etkileşimi
userEvent, arama kutusu ve kupon formu.
userEvent kullanın
Kullanıcı hareketlerini simüle etmek için iki araç vardır.
userEvent kurulumu
Her testte userEvent.setup() çağırın. Bu çağrıyı render çağrısından önce yapın.1 Dönen user nesnesini kullanın:2
const user = userEvent.setup();
render(<CouponForm onApply={vi.fn()} />);
await user.click(screen.getByRole("button", { name: "Uygula" }));Tüm user.* metotları Promise döndürür. Her çağrıya await yazın.3
En sık kullanılan metotlar
| Metot | Ne yapar |
|---|---|
user.click(el) | Elemana tıklar. Checkbox ve radio için de kullanılır.3 |
user.type(el, "metin") | Elemana tıklar ve karakter karakter yazar.4 |
user.clear(el) | Alanın içeriğini siler.4 |
user.selectOptions(el, "değer") | <select> içinden seçenek seçer.4 |
user.tab() | Odağı sonraki elemana taşır.3 |
user.keyboard("{Enter}") | Odaktaki elemana klavye tuşu gönderir.5 |
user.dblClick(el): çift tıklama.user.hover(el)veuser.unhover(el): işaretçiyi elemanın üzerine getirir veya çeker.3user.upload(el, file): dosya yükler.4user.paste("metin"): odaktaki elemana metin yapıştırır.6user.typemevcut metni silmez. Yeni karakterleri sona ekler. Dolu bir alanı değiştirmek için önceuser.clearçağırın.4
Arama kutusu testi
Kullanıcı arama kutusuna yazdığında liste filtrelenir. Alanı temizleyince tüm ürünler geri gelir:
test("aramaya göre ürünleri filtreler", async () => {
const user = userEvent.setup();
render(
<ProductList
loadProducts={() => Promise.resolve(products)}
onAddToCart={vi.fn()}
/>,
);
const search = await screen.findByLabelText("Ürün ara");
await user.type(search, "klavye");
expect(screen.getAllByRole("article")).toHaveLength(1);
expect(
screen.queryByRole("heading", { name: "Oyuncu Faresi" }),
).not.toBeInTheDocument();
await user.clear(search);
expect(screen.getAllByRole("article")).toHaveLength(3);
});Klavye erişimi testi
Kullanıcıların bir bölümü fare kullanmaz.7 Aynı işlemi klavye ile yapın. İlk Tab arama kutusuna, ikinci Tab ilk ürünün butonuna gider:3
test("klavye ile ürünü sepete ekler", async () => {
const user = userEvent.setup();
const onAddToCart = vi.fn();
render(
<ProductList
loadProducts={() => Promise.resolve(products)}
onAddToCart={onAddToCart}
/>,
);
await screen.findAllByRole("article");
await user.tab();
await user.tab();
await user.keyboard("{Enter}");
expect(onAddToCart).toHaveBeenCalledWith(products[0]);
});Form testi: kupon kodu
Sepet sayfasında kullanıcı bir kupon kodu girer. Boş kod hata verir. Kod, baştaki ve sondaki boşluklar silinerek gönderilir.
import { useState, type FormEvent } from "react";
type CouponFormProps = {
onApply: (code: string) => void;
};
export function CouponForm({ onApply }: CouponFormProps) {
const [code, setCode] = useState("");
const [error, setError] = useState<string | null>(null);
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const trimmedCode = code.trim();
if (!trimmedCode) {
setError("Kupon kodu zorunludur.");
return;
}
setError(null);
onApply(trimmedCode);
}
return (
<form onSubmit={handleSubmit} noValidate>
<label htmlFor="coupon">Kupon kodu</label>
<input
id="coupon"
value={code}
onChange={(event) => setCode(event.target.value)}
aria-invalid={error ? true : undefined}
aria-describedby={error ? "coupon-error" : undefined}
/>
{error ? (
<p id="coupon-error" role="alert">
{error}
</p>
) : null}
<button type="submit">Uygula</button>
</form>
);
}Başarılı gönderimi tıklama ve Enter ile test edin:
test("kullanıcı kodu yazıp gönderir", async () => {
const user = userEvent.setup();
const onApply = vi.fn();
render(<CouponForm onApply={onApply} />);
await user.type(screen.getByLabelText("Kupon kodu"), "YAZ2026");
await user.click(screen.getByRole("button", { name: "Uygula" }));
expect(onApply).toHaveBeenCalledWith("YAZ2026");
});
test("Enter tuşu formu gönderir", async () => {
const user = userEvent.setup();
const onApply = vi.fn();
render(<CouponForm onApply={onApply} />);
await user.type(screen.getByLabelText("Kupon kodu"), "YAZ2026{Enter}");
expect(onApply).toHaveBeenCalledWith("YAZ2026");
});Tip
Bir metin alanında Enter, formdaki ilk gönder butonuna tıklar. Bu buton devre dışıysa form gönderilmez. Formda gönder butonu yoksa Enter yalnızca formda tek bir metin alanı varsa gönderir. Bu davranış HTML standardında tanımlıdır.8
Hata durumunu ve hatanın kaybolmasını test edin:
test("boş kod için hata gösterir ve göndermez", async () => {
const user = userEvent.setup();
const onApply = vi.fn();
render(<CouponForm onApply={onApply} />);
await user.click(screen.getByRole("button", { name: "Uygula" }));
expect(screen.getByRole("alert")).toHaveTextContent("Kupon kodu zorunludur.");
expect(screen.getByLabelText("Kupon kodu")).toBeInvalid();
expect(onApply).not.toHaveBeenCalled();
});
test("geçerli kod girilince hata kaybolur", async () => {
const user = userEvent.setup();
render(<CouponForm onApply={vi.fn()} />);
await user.click(screen.getByRole("button", { name: "Uygula" }));
await user.type(screen.getByLabelText("Kupon kodu"), "YAZ2026");
await user.click(screen.getByRole("button", { name: "Uygula" }));
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
});toBeInvalid(), aria-invalid="true" olan veya checkValidity() sonucu false olan elemanı geçersiz sayar. Bu alanda required gibi bir kısıt olmadığı için checkValidity() true döner, test aria-invalid sayesinde geçer.9
Sonucu kontrol edin
Kullanıcının gördüğü sonucu kontrol edin.10
Kendin dene
CouponForm boşlukları siler. Bunu test edin:
Kullanıcı
" YAZ2026 "yazar ve gönderir.onApplyfonksiyonu"YAZ2026"ile çağrılır.
Kaynaklar
`fireEvent` ile farkı, `userEvent.setup()` ve `render` sırası.
`setup()` ile oluşturulan örnek ve doğrudan API'lerle farkı.
`click`, `dblClick`, `hover`, `unhover` ve `tab`.
`type`, `clear`, `selectOptions` ve `upload`.
`keyboard` ve tuş sözdizimi.
`paste`.
Tüm işlevlerin klavyeyle yapılabilmesi ve bunun kimlere fayda sağladığı.
`Enter` ile örtük form gönderimi kuralları.
`toBeInvalid`, `toHaveTextContent`, `toHaveValue` ve `toBeDisabled`.
Kullanıcının görmediği iç state'i test etmenin sorunları.
Last updated: October 14, 2026
