Test Piramidi (Jest/RTL/Cypress)
Ortak konu · İlan 1 İlan 2 İlan 3

Test piramidi: Jest, RTL, Cypress

Üç araç üç farklı katmanı test eder. İlan "Unit / Integration / E2E Testing" diye ayrı ayrı saydığı için mülakatta "hangisini ne zaman kullanırsın" sorusu neredeyse kesin gelir.

E2E · Cypress — az sayıda, yavaş, pahalı
Integration · React Testing Library — component + hook birlikte
Unit · Jest — çok sayıda, hızlı, ucuz
💡
Bu sayfadaki kodlar sahte değil — çalıştırsrc/lib/priceUtils.ts + priceUtils.test.ts ve src/components/ui/Buton.tsx + Buton.test.tsx bu projenin içinde gerçekten var. Terminalde npm test yaz, 5 testin de geçtiğini gör.

1. Jest — birim (unit) test

Tek bir fonksiyonu, dış dünyadan izole şekilde test eder. Hızlıdır (milisaniyeler), network/DB yoktur. "Bu fonksiyon doğru hesaplıyor mu?" sorusuna cevap verir.

// priceUtils.ts
export function applyDiscount(price: number, pct: number) {
  if (pct < 0 || pct > 100) throw new Error("Geçersiz yüzde");
  return Math.round(price * (1 - pct / 100));
}

// priceUtils.test.ts
import { applyDiscount } from "./priceUtils";

describe("applyDiscount", () => {
  it("yüzde 10 indirimi doğru hesaplar", () => {
    expect(applyDiscount(1000, 10)).toBe(900);
  });
  it("geçersiz yüzdede hata fırlatır", () => {
    expect(() => applyDiscount(1000, 150)).toThrow("Geçersiz yüzde");
  });
});

2. React Testing Library — component testi

Bir component'i gerçek bir DOM'a render edip, kullanıcının göreceği şeyler üzerinden (metin, rol, label) etkileşim kurar. RTL'in felsefesi: "implementation detail değil, davranışı test et."

// Buton.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";
import { Buton } from "./Buton";

test("tıklanınca onClick çağrılır", () => {
  const onClick = jest.fn();
  render(<Buton etiket="Kaydet" onClick={onClick} />);

  // Kullanıcı gibi sorgula: className değil, GÖRÜNEN metin/rol
  fireEvent.click(screen.getByRole("button", { name: "Kaydet" }));

  expect(onClick).toHaveBeenCalledTimes(1);
});
💡
RTL'de altın kuralgetByTestId son çare olmalı. Önce getByRole, getByLabelText, getByText dene — bunlar erişilebilirlik (accessibility) ağacını kullanır, yani testin geçmesi aynı zamanda sayfanın ekran okuyucu için de doğru kurulduğunu kanıtlar.

3. Cypress — uçtan uca (E2E) test

Gerçek bir tarayıcıda, gerçek (ya da mock'lanmış) backend'e karşı tüm kullanıcı akışını test eder. İlandaki "fatura ödeme, TL yükleme" gibi kritik akışlar için birebir bu katman kullanılır.

// cypress/e2e/fatura-odeme.cy.ts
describe("Fatura ödeme akışı", () => {
  it("kullanıcı faturasını başarıyla öder", () => {
    cy.visit("/fatura-odeme");
    cy.get('[data-testid="fatura-no"]').type("123456789");
    cy.get('[data-testid="devam-btn"]').click();
    cy.get('[data-testid="odeme-tutari"]').should("contain", "₺");
    cy.get('[data-testid="ode-btn"]').click();
    cy.contains("Ödemeniz başarıyla alındı").should("be.visible");
  });
});
⚠️
Neden piramidin tepesi dar?E2E testler yavaş çalışır (saniyeler), flaky olabilir (network/timing kaynaklı rastgele başarısızlık) ve bakım maliyeti yüksektir. Bu yüzden sadece kritik akışları (ödeme, giriş, satın alma) E2E ile, geri kalan mantığı unit/integration ile kapatmak best practice.

Egzersizler

1) 'Fatura ödeme tutarını hesaplayan bir fonksiyonum var' desen, hangi katmanda test edersin? Ya 'ödeme butonuna basınca API'ye doğru istek gidiyor mu' desen?
İlki saf bir hesaplama fonksiyonu → Jest unit test. İkincisi component + kullanıcı etkileşimi + (mocklanmış) network çağrısı → RTL integration test. Gerçek tarayıcıda uçtan uca "fatura no gir → öde → başarı mesajı gör" akışının tamamı ise Cypress E2E.
2) Bir testin 'flaky' (bazen geçip bazen kalan) olduğunu fark ettin. Bu genelde hangi test katmanında olur ve tipik sebebi nedir?
Çoğunlukla E2E katmanında görülür. Tipik sebepler: sabit sleep/timeout kullanmak yerine elementin gerçekten hazır olmasını beklememek (race condition), test ortamındaki gerçek network gecikmesi, veya testler arası paylaşılan state (temizlenmeyen DB/localStorage). Çözüm: Cypress'in kendi cy.should() retry mekanizmasına güvenmek, sabit wait(ms) yerine görünür/erişilebilir olmayı beklemek.