Türkçe

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:

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

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ı:

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:

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:

İ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:

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:

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:

Etkili Testler Yazmak İçin En İyi Uygulamalar

RTL ile test yazarken izlenecek bazı en iyi uygulamalar şunlardı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.