Component Testing
Şimdiye kadar yazdığımız her component'i TARAYICIDA elle tıklayarak kontrol ettik. Bu, birkaç component için yeterli olsa da, uygulama büyüdükçe her değişiklikten sonra her ekranı elle kontrol etmek hem yavaş hem de güvenilmez. Bu ders, component'lerin doğru çalıştığını OTOMATİK olarak, kod ile doğrulamayı öğretiyor.
Vitest ve React Testing Library Kurulumu
Bu kursta iki kütüphane kullanıyoruz:
- Vitest — testleri ÇALIŞTIRAN araç (
describe,it,expectgibi fonksiyonları sağlar). Vite tabanlı projeler için tasarlandığı için ek bir yapılandırmaya neredeyse hiç ihtiyaç duymaz. - React Testing Library (RTL) — component'leri sahte bir DOM'a (jsdom) YERLEŞTİRİP, o DOM'u gerçek bir kullanıcının göreceği şekilde SORGULAMAMIZI sağlayan kütüphane.
Bir Vite projesine eklemek için:
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
vite.config.js içine bir test bloğu eklenir:
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
setupFiles: ["./src/setupTests.js"],
globals: true,
},
});
environment: "jsdom" testlerin gerçek bir tarayıcı yerine, Node
içinde ÇALIŞAN sahte bir DOM'da koşmasını sağlar. setupFiles
içindeki dosyada tek bir satır yeterli:
import "@testing-library/jest-dom/vitest";
Bu satır, birazdan göreceğimiz toBeInTheDocument() gibi ek
doğrulamaları (matcher) Vitest'in expect'ine EKLER.
render() ve screen ile İlk Testimiz
Bir testin en temel iskeleti; component'i sahte DOM'a yerleştirmek ve içinde beklediğimiz bir şeyin olduğunu doğrulamaktan oluşur:
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
// Test edilecek component. Gerçek bir projede bu genelde ayrı bir dosyada
// (Counter.jsx) olur, testi de ayrı bir dosyada (Counter.test.jsx) yazılır --
// burada tek bir okunabilir örnek olması için ikisini birleştirdik.
function Counter() {
return (
<div>
<p>Count: 0</p>
</div>
);
}
describe("Counter", () => {
it("renders the initial count", () => {
// render(), component'i gerçek bir DOM'a (jsdom, tarayıcı SİMÜLASYONU) yerleştirir.
render(<Counter />);
// screen, o anki DOM'u SORGULAMAK için kullanılır. getByText, tam olarak bu
// metni içeren bir eleman bulamazsa testi ANINDA başarısız yapar.
expect(screen.getByText("Count: 0")).toBeInTheDocument();
});
});
describe, ilgili testleri bir grup altında toplar; it (ya da
test), tek bir test senaryosunu tanımlar. render(<Counter />),
component'i jsdom'a yerleştirir. screen, o anki DOM'u SORGULAMAK
için kullanılır -- getByText, verilen metni içeren bir eleman
bulamazsa test ANINDA başarısız olur.
getByRole ve getByLabelText ile Sorgulama
getByText her zaman en doğru sorgu değildir -- RTL, gerçek
kullanıcıların (ve ekran okuyucuların) sayfayı nasıl ALGILADIĞINA
daha yakın sorgular sunar:
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
function LoginButton() {
return <button>Log In</button>;
}
function NameField() {
return (
<div>
<label htmlFor="name">Name</label>
<input id="name" defaultValue="Ada" />
</div>
);
}
describe("Querying elements", () => {
it("finds a button by its accessible role and name", () => {
render(<LoginButton />);
// getByRole, elemanları GÖRÜNEN metinden değil, ERİŞİLEBİLİRLİK rolünden
// bulur -- bir <button>, "button" rolüne sahiptir. Bu, gerçek kullanıcıların
// (ve ekran okuyucuların) sayfayı nasıl algıladığına en yakın sorgu şeklidir.
expect(screen.getByRole("button", { name: /log in/i })).toBeInTheDocument();
});
it("finds a form field by its connected label", () => {
render(<NameField />);
// getByLabelText, <label htmlFor="..."> ile eşleşen input'u bulur --
// input'un id'sini veya bir test-id eklemeye gerek kalmaz.
expect(screen.getByLabelText("Name")).toHaveValue("Ada");
});
});
getByRole("button", { name: /log in/i }), bir <button> elemanını
ERİŞİLEBİLİRLİK rolünden ve görünen adından bulur -- RTL'in resmî
dokümantasyonu, mümkün olduğunda getByRole'ü ÖNCELİKLİ sorgu olarak
önerir. getByLabelText("Name"), <label htmlFor="name"> ile
eşleşen input'u, id veya test-id eklemeye gerek kalmadan bulur.
jest-dom Matcher'ları
Kurulumda eklediğimiz @testing-library/jest-dom/vitest, expect'e
DOM'a özel yeni doğrulamalar ekler:
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
function SubmitButton({ disabled }) {
return <button disabled={disabled}>Submit</button>;
}
describe("SubmitButton", () => {
it("is disabled when the disabled prop is true", () => {
render(<SubmitButton disabled={true} />);
// toBeDisabled/toBeEnabled/toBeInTheDocument, @testing-library/jest-dom'un
// eklediği matcher'lardır -- düz Vitest'te yok, jsdom kullanan projelerde
// ayrıca kurulur (setupFiles içinde "@testing-library/jest-dom/vitest").
expect(screen.getByRole("button", { name: /submit/i })).toBeDisabled();
});
it("is enabled when the disabled prop is false", () => {
render(<SubmitButton disabled={false} />);
expect(screen.getByRole("button", { name: /submit/i })).toBeEnabled();
});
});
toBeDisabled() ve toBeEnabled(), bir elemanın disabled
özniteliğini kontrol eder; toBeInTheDocument() bir elemanın DOM'da
var olup olmadığını doğrular. Bunlar düz Vitest'te YOKTUR -- jest-dom
paketinin eklediği, DOM testleri için özel olarak tasarlanmış
matcher'lardır.
Koşullu Render'ı Test Etmek
State & Events dersinde gördüğümüz koşullu render deseni, en sık test edilen senaryolardan biridir -- her durumun DOĞRU metni gösterdiğini ayrı ayrı doğrularız:
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
// State & Events dersindeki koşullu render deseninin test edilmiş hali.
function StatusMessage({ status }) {
if (status === "loading") return <p>Loading...</p>;
if (status === "error") return <p>Something went wrong.</p>;
return <p>Data loaded successfully.</p>;
}
describe("StatusMessage", () => {
it("shows a loading message", () => {
render(<StatusMessage status="loading" />);
expect(screen.getByText("Loading...")).toBeInTheDocument();
// queryByText, getByText'in aksine bulamazsa HATA FIRLATMAZ -- null döner.
// Bir şeyin EKRANDA OLMADIĞINI doğrulamak için queryBy* kullanılır.
expect(screen.queryByText("Data loaded successfully.")).not.toBeInTheDocument();
});
it("shows an error message", () => {
render(<StatusMessage status="error" />);
expect(screen.getByText("Something went wrong.")).toBeInTheDocument();
});
it("shows the success message by default", () => {
render(<StatusMessage status="success" />);
expect(screen.getByText("Data loaded successfully.")).toBeInTheDocument();
});
});
Üç ayrı it bloğu, status prop'unun üç farklı değeri için
component'i ayrı ayrı render edip doğru mesajın göründüğünü
kontrol ediyor. İlk testte ayrıca queryByText kullanılıyor:
getByText'in aksine, eleman bulunamazsa hata FIRLATMAZ, null
döner -- bu yüzden bir şeyin EKRANDA OLMADIĞINI doğrulamak için
getByText değil queryByText kullanılır.
Özet ve Terimler Sözlüğü
Vitest testleri ÇALIŞTIRIR, React Testing Library component'leri
sahte bir DOM'a yerleştirip SORGULAMAMIZI sağlar. render() bir
component'i DOM'a yerleştirir; screen o DOM'u sorgulamak için
kullanılır. getByRole/getByLabelText/getByText, bir eleman
bulamazsa hata fırlatır; queryBy* varyantları bulamazsa null
döner ve bir şeyin EKRANDA OLMADIĞINI doğrulamak için kullanılır.
@testing-library/jest-dom, toBeInTheDocument() gibi DOM'a özel
matcher'lar ekler.
Terimler Sözlüğü
Test Runner — Testleri bulup çalıştıran, sonuçları raporlayan araç (Vitest).
jsdom — Node içinde çalışan, gerçek bir tarayıcıyı SİMÜLE eden sahte bir DOM ortamı.
Matcher — expect(...)'ten sonra zincirlenen, belirli bir koşulu
doğrulayan fonksiyon (toBeInTheDocument(), toHaveValue() gibi).