Bu kapsamlı rehberle React Testing Library'de (RTL) ustalaşın. React uygulamalarınız için etkili, sürdürülebilir ve kullanıcı odaklı testler yazmayı öğrenin.
React Testing Library: Kapsamlı Bir Rehber
Günümüzün hızlı ilerleyen web geliştirme dünyasında, React uygulamalarınızın kalitesini ve güvenilirliğini sağlamak büyük önem taşır. React Testing Library (RTL), kullanıcı perspektifine odaklanan testler yazmak için popüler ve etkili bir çözüm olarak ortaya çıkmıştır. Bu rehber, temel kavramlardan ileri tekniklere kadar RTL'ye tam bir genel bakış sunarak, sağlam ve sürdürülebilir React uygulamaları oluşturmanıza olanak tanır.
Neden React Testing Library'yi Seçmelisiniz?
Geleneksel test yaklaşımları genellikle uygulama detaylarına dayanır, bu da testleri kırılgan hale getirir ve küçük kod değişiklikleriyle bozulmaya yatkın kılar. RTL ise, bileşenlerinizi bir kullanıcının onlarla etkileşime gireceği şekilde test etmenizi teşvik ederek, kullanıcının gördüklerine ve deneyimlediklerine odaklanır. Bu yaklaşım birkaç temel avantaj sunar:
- Kullanıcı Odaklı Test: RTL, kullanıcının bakış açısını yansıtan testler yazmayı teşvik ederek, uygulamanızın son kullanıcının gözünden beklendiği gibi çalışmasını sağlar.
- Test Kırılganlığının Azaltılması: Uygulama detaylarını test etmekten kaçınarak, RTL testlerinin kodunuzu yeniden düzenlediğinizde bozulma olasılığı daha düşüktür, bu da daha sürdürülebilir ve sağlam testlere yol açar.
- İyileştirilmiş Kod Tasarımı: RTL, sizi erişilebilir ve kullanımı kolay bileşenler yazmaya teşvik ederek genel kod tasarımının daha iyi olmasına yol açar.
- Erişilebilirliğe Odaklanma: RTL, bileşenlerinizin erişilebilirliğini test etmeyi kolaylaştırarak, uygulamanızın herkes tarafından kullanılabilir olmasını sağlar.
- Basitleştirilmiş Test Süreci: RTL, basit ve sezgisel bir API sağlayarak test yazmayı ve bakımını kolaylaştırır.
Test Ortamınızı Kurma
RTL kullanmaya başlamadan önce test ortamınızı kurmanız gerekir. Bu genellikle gerekli bağımlılıkları yüklemeyi ve test çatınızı (framework) yapılandırmayı içerir.
Ön Koşullar
- Node.js ve npm (veya yarn): Sisteminizde Node.js ve npm'in (veya yarn'ın) kurulu olduğundan emin olun. Bunları resmi Node.js web sitesinden indirebilirsiniz.
- React Projesi: Mevcut bir React projeniz olmalı veya Create React App ya da benzer bir araç kullanarak yeni bir tane oluşturmalısınız.
Kurulum
Aşağıdaki paketleri npm veya yarn kullanarak yükleyin:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Veya, yarn kullanarak:
yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Paketlerin Açıklaması:
- @testing-library/react: React bileşenlerini test etmek için temel kütüphane.
- @testing-library/jest-dom: DOM düğümleri hakkında iddiada bulunmak için özel Jest eşleştiricileri (matcher) sağlar.
- Jest: Popüler bir JavaScript test çatısı.
- babel-jest: Kodunuzu derlemek için Babel kullanan bir Jest dönüştürücüsü.
- @babel/preset-env: Hedef ortamlarınızı desteklemek için gereken Babel eklentilerini ve ön ayarlarını belirleyen bir Babel ön ayarı.
- @babel/preset-react: React için bir Babel ön ayarı.
Yapılandırma
Projenizin kök dizininde aşağıdaki içeriğe sahip bir `babel.config.js` dosyası oluşturun:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Bir test betiği eklemek için `package.json` dosyanızı güncelleyin:
{
"scripts": {
"test": "jest"
}
}
Jest'i yapılandırmak için projenizin kök dizininde bir `jest.config.js` dosyası oluşturun. Minimal bir yapılandırma şuna benzeyebilir:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
};
Aşağıdaki içeriğe sahip bir `src/setupTests.js` dosyası oluşturun. Bu, Jest DOM eşleştiricilerinin tüm testlerinizde mevcut olmasını sağlar:
import '@testing-library/jest-dom/extend-expect';
İlk Testinizi Yazma
Basit bir örnekle başlayalım. Bir selamlama mesajı gösteren bir React bileşeniniz olduğunu varsayalım:
// src/components/Greeting.js
import React from 'react';
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default Greeting;
Şimdi, bu bileşen için bir test yazalım:
// src/components/Greeting.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';
test('renders a greeting message', () => {
render(<Greeting name="World" />);
const greetingElement = screen.getByText(/Hello, World!/i);
expect(greetingElement).toBeInTheDocument();
});
Açıklama:
- `render`: Bu fonksiyon, bileşeni DOM'a render eder.
- `screen`: Bu nesne, DOM'u sorgulamak için yöntemler sağlar.
- `getByText`: Bu yöntem, bir öğeyi metin içeriğine göre bulur. `/i` bayrağı, aramayı büyük/küçük harfe duyarsız hale getirir.
- `expect`: Bu fonksiyon, bileşenin davranışı hakkında iddialarda bulunmak için kullanılır.
- `toBeInTheDocument`: Bu eşleştirici, öğenin DOM'da mevcut olduğunu iddia eder.
Testi çalıştırmak için terminalinizde aşağıdaki komutu yürütün:
npm test
Her şey doğru yapılandırıldıysa, testin geçmesi gerekir.
Yaygın RTL Sorguları
RTL, DOM'daki öğeleri bulmak için çeşitli sorgu yöntemleri sunar. Bu sorgular, kullanıcıların uygulamanızla nasıl etkileşim kurduğunu taklit etmek için tasarlanmıştır.
`getByRole`
Bu sorgu, bir öğeyi ARIA rolüne göre bulur. Mümkün olduğunda `getByRole` kullanmak iyi bir uygulamadır, çünkü erişilebilirliği teşvik eder ve testlerinizin altta yatan DOM yapısındaki değişikliklere karşı dirençli olmasını sağlar.
<button role="button">Click me</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();
`getByLabelText`
Bu sorgu, bir öğeyi ilişkili etiketinin metnine göre bulur. Form öğelerini test etmek için kullanışlıdır.
<label htmlFor="name">Name:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Name:');
expect(nameInputElement).toBeInTheDocument();
`getByPlaceholderText`
Bu sorgu, bir öğeyi yer tutucu metnine göre bulur.
<input type="text" placeholder="Enter your email" />
const emailInputElement = screen.getByPlaceholderText('Enter your email');
expect(emailInputElement).toBeInTheDocument();
`getByAltText`
Bu sorgu, bir resim öğesini alt metnine göre bulur. Erişilebilirliği sağlamak için tüm resimlere anlamlı alt metin sağlamak önemlidir.
<img src="logo.png" alt="Company Logo" />
const logoImageElement = screen.getByAltText('Company Logo');
expect(logoImageElement).toBeInTheDocument();
`getByTitle`
Bu sorgu, bir öğeyi başlık (title) özniteliğine göre bulur.
<span title="Close">X</span>
const closeElement = screen.getByTitle('Close');
expect(closeElement).toBeInTheDocument();
`getByDisplayValue`
Bu sorgu, bir öğeyi görüntülenen değerine göre bulur. Bu, önceden doldurulmuş değerlere sahip form girişlerini test etmek için kullanışlıdır.
<input type="text" value="Initial Value" />
const inputElement = screen.getByDisplayValue('Initial Value');
expect(inputElement).toBeInTheDocument();
`getAllBy*` Sorguları
`getBy*` sorgularına ek olarak, RTL ayrıca eşleşen öğelerin bir dizisini döndüren `getAllBy*` sorguları da sağlar. Bunlar, aynı özelliklere sahip birden fazla öğenin DOM'da mevcut olduğunu iddia etmeniz gerektiğinde kullanışlıdır.
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
`queryBy*` Sorguları
`queryBy*` sorguları `getBy*` sorgularına benzer, ancak bir hata fırlatmak yerine eşleşen öğe bulunamazsa `null` döndürürler. Bu, bir öğenin DOM'da bulunmadığını iddia etmek istediğinizde kullanışlıdır.
const missingElement = screen.queryByText('Non-existent text');
expect(missingElement).toBeNull();
`findBy*` Sorguları
`findBy*` sorguları, `getBy*` sorgularının asenkron versiyonlarıdır. Eşleşen öğe bulunduğunda çözümlenen bir Promise döndürürler. Bunlar, bir API'den veri getirme gibi asenkron işlemleri test etmek için kullanışlıdır.
// Asenkron bir veri getirme simülasyonu
const fetchData = () => new Promise(resolve => {
setTimeout(() => resolve('Data Loaded!'), 1000);
});
function MyComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data}</div>;
}
test('loads data asynchronously', async () => {
render(<MyComponent />);
const dataElement = await screen.findByText('Data Loaded!');
expect(dataElement).toBeInTheDocument();
});
Kullanıcı Etkileşimlerini Simüle Etme
RTL, düğmelere tıklama, giriş alanlarına yazma ve formları gönderme gibi kullanıcı etkileşimlerini simüle etmek için `fireEvent` ve `userEvent` API'lerini sağlar.
`fireEvent`
`fireEvent`, DOM olaylarını programlı olarak tetiklemenize olanak tanır. Tetiklenen olaylar üzerinde hassas kontrol sağlayan daha düşük seviyeli bir API'dir.
<button onClick={() => alert('Button clicked!')}>Click me</button>
import { fireEvent } from '@testing-library/react';
test('simulates a button click', () => {
const alertMock = jest.spyOn(window, 'alert').mockImplementation(() => {});
render(<button onClick={() => alert('Button clicked!')}>Click me</button>);
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(alertMock).toHaveBeenCalledTimes(1);
alertMock.mockRestore();
});
`userEvent`
`userEvent`, kullanıcı etkileşimlerini daha gerçekçi bir şekilde simüle eden daha yüksek seviyeli bir API'dir. Odak yönetimi ve olay sıralaması gibi ayrıntıları ele alarak testlerinizi daha sağlam ve daha az kırılgan hale getirir.
<input type="text" onChange={e => console.log(e.target.value)} />
import userEvent from '@testing-library/user-event';
test('simulates typing in an input field', () => {
const inputElement = screen.getByRole('textbox');
userEvent.type(inputElement, 'Hello, world!');
expect(inputElement).toHaveValue('Hello, world!');
});
Asenkron Kodu Test Etme
Birçok React uygulaması, bir API'den veri getirme gibi asenkron işlemler içerir. RTL, asenkron kodu test etmek için çeşitli araçlar sağlar.
`waitFor`
`waitFor`, bir iddiada bulunmadan önce bir koşulun doğru olmasını beklemenize olanak tanır. Tamamlanması biraz zaman alan asenkron işlemleri test etmek için kullanışlıdır.
function MyComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
setTimeout(() => {
setData('Data loaded!');
}, 1000);
}, []);
return <div>{data}</div>;
}
import { waitFor } from '@testing-library/react';
test('waits for data to load', async () => {
render(<MyComponent />);
await waitFor(() => screen.getByText('Data loaded!'));
const dataElement = screen.getByText('Data loaded!');
expect(dataElement).toBeInTheDocument();
});
`findBy*` Sorguları
Daha önce belirtildiği gibi, `findBy*` sorguları asenkrondur ve eşleşen öğe bulunduğunda çözümlenen bir Promise döndürür. Bunlar, DOM'da değişikliklere neden olan asenkron işlemleri test etmek için kullanışlıdır.
Hook'ları Test Etme
React Hook'ları, durum bilgisi olan mantığı kapsülleyen yeniden kullanılabilir fonksiyonlardır. RTL, özel Hook'ları izolasyonda test etmek için `@testing-library/react-hooks`'tan (v17 itibarıyla doğrudan `@testing-library/react` lehine kullanımdan kaldırılmıştır) `renderHook` yardımcı programını sağlar.
// src/hooks/useCounter.js
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => {
setCount(prevCount => prevCount + 1);
};
const decrement = () => {
setCount(prevCount => prevCount - 1);
};
return { count, increment, decrement };
}
export default useCounter;
// src/hooks/useCounter.test.js
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';
test('increments the counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Açıklama:
- `renderHook`: Bu fonksiyon, Hook'u render eder ve Hook'un sonucunu içeren bir nesne döndürür.
- `act`: Bu fonksiyon, durum güncellemelerine neden olan herhangi bir kodu sarmak için kullanılır. Bu, React'in güncellemeleri düzgün bir şekilde toplamasını ve işlemesini sağlar.
İleri Düzey Test Teknikleri
RTL'nin temellerinde ustalaştıktan sonra, testlerinizin kalitesini ve sürdürülebilirliğini artırmak için daha ileri düzey test tekniklerini keşfedebilirsiniz.
Modülleri Mock'lama (Taklit Etme)
Bazen, bileşenlerinizi izole etmek ve test sırasında davranışlarını kontrol etmek için harici modülleri veya bağımlılıkları taklit etmeniz (mock) gerekebilir. Jest, bu amaç için güçlü bir taklit etme API'si sağlar.
// src/api/dataService.js
export const fetchData = async () => {
const response = await fetch('/api/data');
const data = await response.json();
return data;
};
// src/components/MyComponent.js
import React, { useState, useEffect } from 'react';
import { fetchData } from '../api/dataService';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data}</div>;
}
// src/components/MyComponent.test.js
import { render, screen, waitFor } from '@testing-library/react';
import MyComponent from './MyComponent';
import * as dataService from '../api/dataService';
jest.mock('../api/dataService');
test('fetches data from the API', async () => {
dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' });
render(<MyComponent />);
await waitFor(() => screen.getByText('Mocked data!'));
expect(screen.getByText('Mocked data!')).toBeInTheDocument();
expect(dataService.fetchData).toHaveBeenCalledTimes(1);
});
Açıklama:
- `jest.mock('../api/dataService')`: Bu satır, `dataService` modülünü taklit eder.
- `dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' })`: Bu satır, taklit edilen `fetchData` fonksiyonunu, belirtilen verilerle çözümlenen bir Promise döndürecek şekilde yapılandırır.
- `expect(dataService.fetchData).toHaveBeenCalledTimes(1)`: Bu satır, taklit edilen `fetchData` fonksiyonunun bir kez çağrıldığını iddia eder.
Context Sağlayıcıları (Providers)
Bileşeniniz bir Context Sağlayıcısına (Provider) dayanıyorsa, test sırasında bileşeninizi sağlayıcıya sarmanız gerekir. Bu, bileşenin bağlam (context) değerlerine erişmesini sağlar.
// src/contexts/ThemeContext.js
import React, { createContext, useState } from 'react';
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// src/components/MyComponent.js
import React, { useContext } from 'react';
import { ThemeContext } from '../contexts/ThemeContext';
function MyComponent() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div style={{ backgroundColor: theme === 'light' ? '#fff' : '#000', color: theme === 'light' ? '#000' : '#fff' }}>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
// src/components/MyComponent.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
import { ThemeProvider } from '../contexts/ThemeContext';
test('toggles the theme', () => {
render(
<ThemeProvider>
<MyComponent />
</ThemeProvider>
);
const themeParagraph = screen.getByText(/Current theme: light/i);
const toggleButton = screen.getByRole('button', { name: /Toggle Theme/i });
expect(themeParagraph).toBeInTheDocument();
fireEvent.click(toggleButton);
expect(screen.getByText(/Current theme: dark/i)).toBeInTheDocument();
});
Açıklama:
- Test sırasında gerekli bağlamı sağlamak için `MyComponent`'i `ThemeProvider` ile sarıyoruz.
Router ile Test Etme
React Router kullanan bileşenleri test ederken, sahte bir Router bağlamı sağlamanız gerekir. Bunu `react-router-dom`'dan `MemoryRouter` bileşenini kullanarak başarabilirsiniz.
// src/components/MyComponent.js
import React from 'react';
import { Link } from 'react-router-dom';
function MyComponent() {
return (
<div>
<Link to="/about">Go to About Page</Link>
</div>
);
}
// src/components/MyComponent.test.js
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import MyComponent from './MyComponent';
test('renders a link to the about page', () => {
render(
<MemoryRouter>
<MyComponent />
</MemoryRouter>
);
const linkElement = screen.getByRole('link', { name: /Go to About Page/i });
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', '/about');
});
Açıklama:
- Sahte bir Router bağlamı sağlamak için `MyComponent`'i `MemoryRouter` ile sarıyoruz.
- Bağlantı öğesinin doğru `href` özniteliğine sahip olduğunu iddia ediyoruz.
Etkili Testler Yazmak İçin En İyi Uygulamalar
RTL ile test yazarken izlenecek bazı en iyi uygulamalar şunlardır:
- Kullanıcı Etkileşimlerine Odaklanın: Kullanıcıların uygulamanızla nasıl etkileşim kurduğunu simüle eden testler yazın.
- Uygulama Detaylarını Test Etmekten Kaçının: Bileşenlerinizin iç işleyişini test etmeyin. Bunun yerine, gözlemlenebilir davranışa odaklanın.
- Açık ve Öz Testler Yazın: Testlerinizi anlaşılması ve bakımı kolay hale getirin.
- Anlamlı Test Adları Kullanın: Test edilen davranışı doğru bir şekilde tanımlayan test adları seçin.
- Testleri İzole Tutun: Testler arasında bağımlılıklardan kaçının. Her test bağımsız ve kendi kendine yeterli olmalıdır.
- Uç Durumları Test Edin: Sadece mutlu yolu test etmeyin. Uç durumları ve hata koşullarını da test ettiğinizden emin olun.
- Kodlamadan Önce Test Yazın: Kodunuzu yazmadan önce test yazmak için Test Güdümlü Geliştirmeyi (TDD) kullanmayı düşünün.
- "AAA" Modelini Takip Edin: Arrange, Act, Assert (Hazırla, Eyleme Geç, Doğrula). Bu model, testlerinizi yapılandırmaya ve daha okunabilir hale getirmeye yardımcı olur.
- Testlerinizi hızlı tutun: Yavaş testler, geliştiricileri sık sık çalıştırmaktan caydırabilir. Ağ isteklerini taklit ederek ve DOM manipülasyonu miktarını en aza indirerek testlerinizi hız için optimize edin.
- Açıklayıcı hata mesajları kullanın: İddialar başarısız olduğunda, hata mesajları başarısızlığın nedenini hızlı bir şekilde belirlemek için yeterli bilgi sağlamalıdır.
Sonuç
React Testing Library, React uygulamalarınız için etkili, sürdürülebilir ve kullanıcı merkezli testler yazmak için güçlü bir araçtır. Bu kılavuzda özetlenen ilke ve teknikleri izleyerek, kullanıcılarınızın ihtiyaçlarını karşılayan sağlam ve güvenilir uygulamalar oluşturabilirsiniz. Kullanıcının bakış açısından test etmeye odaklanmayı, uygulama ayrıntılarını test etmekten kaçınmayı ve açık ve özlü testler yazmayı unutmayın. RTL'yi benimseyerek ve en iyi uygulamaları benimseyerek, konumunuzdan veya küresel kitlenizin özel gereksinimlerinden bağımsız olarak React projelerinizin kalitesini ve sürdürülebilirliğini önemli ölçüde artırabilirsiniz.