04 / 12Proje
Ürün Kartı
E-ticaret ürün kartı için adım adım test.
Senaryo
Mağaza sayfasında her ürün bir kartta görünür. Kart şunları yapar:
Ürün adını ve fiyatını gösterir.
Sepete ekle butonuna tıklanınca ürünü bildirir.
Ürün stokta yoksa Stokta yok yazar ve butonu devre dışı bırakır.
Ürün indirimliyse eski fiyatı üstü çizili gösterir.
Component
Bu proje boyunca kullanacağınız son ProductCard sürümü. Önceki sürüme oldPrice ve aria-labelledby eklendi.1 Bu iki ekleme sonraki derslerde işe yarar.
import { formatPrice } from "./format-price";
import type { Product } from "./types";
type ProductCardProps = {
product: Product;
onAddToCart: (product: Product) => void;
};
export function ProductCard({ product, onAddToCart }: ProductCardProps) {
const titleId = `product-${product.id}-title`;
return (
<article aria-labelledby={titleId}>
<h2 id={titleId}>{product.name}</h2>
{product.oldPrice ? <s>{formatPrice(product.oldPrice)}</s> : null}
<p>{formatPrice(product.price)}</p>
{product.inStock ? null : <p>Stokta yok</p>}
<button
type="button"
disabled={!product.inStock}
onClick={() => onAddToCart(product)}
>
Sepete ekle
</button>
</article>
);
}Product tipine isteğe bağlı bir oldPrice alanı ekleyin:
export type Product = {
id: number;
name: string;
price: number;
oldPrice?: number;
inStock: boolean;
};Testler
Testleri tek bir dosyada, adım adım yazın. Önce ortak veriyi tanımlayın:
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ProductCard } from "./ProductCard";
import type { Product } from "./types";
const headphones: Product = {
id: 1,
name: "Kablosuz Kulaklık",
price: 1299,
inStock: true,
};Adım 1: Ürün bilgisi
describe("ProductCard", () => {
test("ürün adını ve fiyatını gösterir", () => {
render(<ProductCard product={headphones} onAddToCart={vi.fn()} />);
expect(
screen.getByRole("heading", { name: "Kablosuz Kulaklık" }),
).toBeInTheDocument();
expect(screen.getByText("₺1.299,00")).toBeInTheDocument();
});
});Sonraki üç testi aynı describe bloğunun içine ekleyin.
Adım 2: Sepete ekleme
test("Sepete ekle butonuna tıklayınca ürünü bildirir", async () => {
const user = userEvent.setup();
const onAddToCart = vi.fn();
render(<ProductCard product={headphones} onAddToCart={onAddToCart} />);
await user.click(screen.getByRole("button", { name: "Sepete ekle" }));
expect(onAddToCart).toHaveBeenCalledWith(headphones);
});Adım 3: Stokta olmayan ürün
test("stokta olmayan ürün için butonu devre dışı bırakır", async () => {
const user = userEvent.setup();
const onAddToCart = vi.fn();
render(
<ProductCard
product={{ ...headphones, inStock: false }}
onAddToCart={onAddToCart}
/>,
);
expect(screen.getByText("Stokta yok")).toBeVisible();
expect(screen.getByRole("button", { name: "Sepete ekle" })).toBeDisabled();
await user.click(screen.getByRole("button", { name: "Sepete ekle" }));
expect(onAddToCart).not.toHaveBeenCalled();
});Adım 4: İndirimli fiyat
test("indirimli üründe eski fiyatı gösterir", () => {
render(
<ProductCard
product={{ ...headphones, oldPrice: 1499 }}
onAddToCart={vi.fn()}
/>,
);
expect(screen.getByText("₺1.499,00")).toBeInTheDocument();
expect(screen.getByText("₺1.299,00")).toBeInTheDocument();
});İpucu
userEvent tarayıcı gibi davranır. Devre dışı butona tıklamak onAddToCart fonksiyonunu çağırmaz.2
Hangi konuyu tekrar ettiniz?
Fiyat metnini
formatPriceile üretmeyin. Testte"₺1.299,00"gibi sabit metin yazın. Biçim bozulursa test başarısız olur ve hatayı gösterir.Fiyat biçimi
Intliçin kullanılan ICU verisine bağlıdır. Bu veri Node.js derlemesine ve sisteme göre değişebilir. Takımınız farklı ortamlar kullanıyorsa CI ortamında bir kez çalıştırarak doğrulayın.5
Kendin dene
Önce testi yazın, sonra component'i değiştirin (TDD). İndirimli ürün için yüzde bilgisi eklenecek.
Test:
price1299,oldPrice1499 olan ürün kartında %13 indirim yazısı görünür.Testin başarısız olduğunu görün.
ProductCardiçine yüzdeyi hesaplayıp gösterin.Testin geçtiğini görün.
Kaynaklar
`article` rolünün adı içerikten değil yazardan gelir, bu yüzden `aria-labelledby` gerekir.
`userEvent` tarayıcının izin vermediği etkileşimi, örneğin devre dışı alanı, yapmaz.
`render`, `screen` ve rol sorgularıyla örnek test.
`toBeVisible` ve `toBeDisabled` matcher'larının kuralları.
`Intl` çıktısının ICU verisine ve Node.js derlemesine bağlılığı.
Son güncelleme tarihi: 14 Ekim 2026
