Uygulamada bileşen testi

Bileşen testi, uygulamalı test kodunu göstermeye başlamak için iyi bir yerdir. Bileşen testleri, basit birim testlerinden daha önemli, uçtan uca testlerden daha az karmaşıktır ve DOM ile etkileşimi gösterir. Daha felsefi bir şekilde, React kullanımı, web geliştiricilerinin web sitelerini veya web uygulamalarını bileşenlerden oluştuğunu düşünmelerini kolaylaştırdı.

Bu nedenle, ne kadar karmaşık olduklarından bağımsız olarak bileşenlerin test edilmesi, yeni veya mevcut bir uygulamayı test etmeyi düşünmeye başlamak için iyi bir yoldur.

Bu sayfada, karmaşık dış bağımlılıklara sahip küçük bir bileşenin nasıl test edileceği adım adım açıklanmıştır. Örneğin, bir düğmeyi tıklayıp bir sayının arttığını onaylayarak başka bir kodla etkileşim kurmayan bileşeni test etmek kolaydır. Gerçekte, çok az kod bu şekildedir ve etkileşim içermeyen kodun değeri sınırlı olabilir.

(Bu, kapsamlı bir eğitim olarak tasarlanmamıştır. Daha sonra yer alan "Otomatik test" adlı pratik test bölümü, eğitim olarak kullanabileceğiniz örnek kodla gerçek bir siteyi test etme konusunda size yol gösterecektir. Ancak, bu sayfada yine de çeşitli pratik bileşen testi örnekleri ele alınacaktır.)

Test edilen bileşen

Bir React bileşenini test etmek için Vitest'i ve JSDOM ortamını kullanacağız. Böylece komut satırında Düğüm kullanarak ve bir tarayıcı emülasyonu yaparken testleri hızlı bir şekilde çalıştırabiliyoruz.

Adların yanında, Seç düğmesi bulunan bir listedir.
Ağdaki kullanıcıların listesini gösteren küçük bir React bileşeni.

UserList adlı bu Tepki bileşeni, ağdan bir kullanıcı listesi getirir ve bu kullanıcılardan birini seçebilmenizi sağlar. Kullanıcı listesi, useEffect içinde fetch kullanılarak alındı ve seçim işleyici Context tarafından geçirildi. Kodu şu şekildedir:

import React, { useEffect, useState, useContext } from 'react';
import { UserContext } from './UserContext.tsx';
import { UserRow } from './UserRow.tsx';

export function UserList({ count = 4 }: { count?: number }) {
  const [users, setUsers] = useState<any[]>([]);
  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users?_limit=' + count)
      .then((response) => response.json())
      .then((json) => setUsers(json));
  }, [count]);

  const c = useContext(UserContext);
  return (
    <div>
      <h2>Users</h2>
      <ul>
        {users.map((u) => (
          <li key={u.id}>
            <button onClick={() => c.userChosen(u.id)}>Choose</button>{' '}
            <UserRow u={u} />
          </li>
        ))}
      </ul>
    </div>
  );
}

Bu örnek, React en iyi uygulamalarını göstermemektedir (örneğin, useEffect içinde fetch kullanılmıştır) ancak kod tabanınız bunun gibi birçok durum içerebilir. Daha net söylemek gerekirse, bu vakalar ilk bakışta test etmek için inatçı görünebilir. Bu kursun sonraki bölümlerinde, test edilebilir kod yazma işlemi ayrıntılı olarak ele alınacaktır.

Bu örnekte test etmekte olduğumuz şeyler şunlardır:

  • Ağdan gelen verilere yanıt olarak doğru DOM'ların oluşturulup oluşturulmadığını kontrol edin.
  • Bir kullanıcının tıklanmasının bir geri çağırmayı tetiklediğini doğrulayın.

Her bileşen farklıdır. Bunu test etmeyi ilginç kılan şey nedir?

  • Ağdan gerçek zamanlı veriler istemek için genel fetch API'sini kullanır. Bu veriler, test sürecinde yavaş veya stabil olmayabilir.
  • Dolaylı olarak test etmek istemeyebileceğimiz başka bir sınıfı (UserRow) içe aktarır.
  • Özel olarak test edilen kodun parçası olmayan ve normalde bir üst bileşen tarafından sağlanan bir Context kullanır.

Başlamak için hızlı bir test yazın

Bu bileşenle ilgili çok temel bir şeyi hızlı bir şekilde test edebiliriz. Açıkçası, bu örnek pek de yararlı değil! Ancak, ortak metni UserList.test.tsx adlı bir eş dosyasında oluşturmak faydalıdır (Vitest gibi test çalıştırıcılarının, varsayılan olarak .tsx dahil .test.js veya benzeriyle biten dosyaları çalıştıracağını unutmayın):

import { vi, test, assert, afterAll } from 'vitest';
import { render } from '@testing-library/react';
import { UserList } from './UserList.tsx';
import React, { ContextType } from 'react';

test('render', async () => {
  const c = render(<UserList />);

  const headingNode = await c.findAllByText(/Users);
  assert.isNotNull(headingNode);
});

Bu test, bileşen oluşturulduğunda "Kullanıcılar" metnini içerdiğini iddia eder. Bileşenin ağa fetch göndermesi gibi bir yan etkisi olsa da ancak çalışır. fetch uç noktası belirlenmeden testin sonunda hâlâ devam ediyor. Test sona erdiğinde herhangi bir kullanıcı bilgisinin gösterildiğini onaylayamayız, en azından zaman aşımı beklemeden bunu yapamayız.

Örnek fetch()

Sahtekarlık, bir test için gerçek bir işlevi veya sınıfı kontrolünüz altındaki bir şeyle değiştirme eylemidir. Bu, en basit birim testleri dışında neredeyse tüm test türlerinde yaygın bir uygulamadır. Bu konu, Onaylar ve diğer temel öğeler bölümünde daha ayrıntılı olarak ele alınacaktır.

Testiniz için fetch() ile taklit edebilirsiniz. Böylece testiniz hızlı bir şekilde tamamlanır ve "gerçek dünyadan" veya bilinmeyen veriler değil, beklediğiniz verileri döndürür. fetch global bir değerdir. Diğer bir deyişle, bunu import veya require kodumuza eklememiz gerekmez.

En güçlü şekilde, vi.stubGlobal yöntemini vi.fn() tarafından döndürülen özel bir nesne ile çağırarak global bir model oluşturabilirsiniz. Bu, daha sonra değiştirebileceğimiz bir model oluşturur. Bu yöntemler, bu kursun sonraki bölümlerinde daha ayrıntılı olarak ele alınacaktır. Ancak bu yöntemleri uygulamalı olarak aşağıdaki kodda görebilirsiniz:

test('render', async () => {
  const fetchMock = vi.fn();
  fetchMock.mockReturnValue(
    Promise.resolve({
      json: () => Promise.resolve([{ name: 'Sam', id: 'sam' }]),
    }),
  );
  vi.stubGlobal('fetch', fetchMock);

  const c = render(<UserList />);

  const headingNode = await c.queryByText(/Users);
  assert.isNotNull(headingNode);

  await waitFor(async () => {
    const samNode = await c.queryByText(/Sam);
    assert.isNotNull(samNode);
  });
});

afterAll(() => {
  vi.unstubAllGlobals();
});

Bu kod bir örnek ekler, Response ağ getirme işleminin "sahte" sürümünü tanımlar ve ardından bunun görünmesini bekler. Metin görünmezse (queryByText içindeki sorguyu yeni bir adla değiştirerek kontrol edebilirsiniz) test başarısız olur.

Bu örnekte Vitest'in yerleşik alay yardımcıları kullanılmış olsa da diğer test çerçevelerinde de benzer yaklaşımlar benimsenmektedir. Vitest, tüm testlerden sonra vi.unstubAllGlobals() yöntemini çağırmanız veya eşdeğer bir genel seçenek belirlemeniz gerektiği açısından benzersizdir. Çalışmamızı "geri almadığımızda" fetch modeli, diğer testleri etkileyebilir ve her istek, tuhaf JSON yığınımızla yanıtlanır.

Örnek ithalat

UserList bileşenimizin UserRow adlı bir bileşeni içe aktardığını fark etmiş olabilirsiniz. Kodu eklememiş olsak da kullanıcının adını oluşturduğunu görebilirsiniz: Önceki test, "Sam" ifadesini kontrol eder ve bu kod doğrudan UserList içinde oluşturulmadığından UserRow öğesinden gelmelidir.

Kullanıcı adlarının bileşenimizde nasıl ilerlediğini gösteren bir akış şeması.
UserListTest, UserRow değerine sahip değil.

Bununla birlikte, UserRow kendisi karmaşık bir bileşen olabilir. Daha fazla kullanıcı verisi getirebilir veya testimizle ilgisi olmayan yan etkileri olabilir. Bu değişkenliği kaldırmak, özellikle test etmek istediğiniz bileşenler daha karmaşık hale geldiğinde ve bağımlılıklarıyla daha iç içe geçtiği için testlerinizi daha faydalı hale getirir.

Neyse ki, testiniz doğrudan kullanmasa bile belirli içe aktarmaların modelini yapmak için Vitest'i kullanabilirsiniz. Böylece, bu içe aktarma işlemlerini kullanan herhangi bir kod, basit veya bilinen bir sürümle sağlanır:

vi.mock('./UserRow.tsx', () => {
  return {
    UserRow(arg) {
      return <>{arg.u.name}</>;
    },
  }
});

test('render', async () => {
  // ...
});

Global fetch ile alay etmek gibi, bu da güçlü bir araçtır. Ancak kodunuzun çok fazla bağımlılığı varsa sürdürülebilir olamayabilir. Bunun en iyi çözümü de test edilebilir kod yazmaktır.

Tıklayın ve bağlam sağlayın

React ve Lit gibi diğer kitaplıkların Context adında bir kavramı vardır. Örnek kod, kullanıcı seçilirse yöntemi çağıran bir UserContext içerir. Bu, genellikle geri çağırmanın doğrudan UserList öğesine iletildiği "kaynak sondajı"na bir alternatif olarak görülür.

Yazdığımız test grubu, UserContext sağlamadı. Tepki testi'ne bu olmadan bir tıklama eylemi eklediğinizde, en kötü ihtimalle testi çöker veya başka bir yerde varsayılan bir örnek sağlandıysa kontrolümüz dışında bazı davranışlara neden olur (yukarıdaki bilinmeyen UserRow özelliğine benzer şekilde):

  const c = render(<UserList />);
  const chooseButton = await c.getByText(/Choose);
  chooseButton.click();

Bunun yerine, bileşeni oluştururken kendi Context sağlayabilirsiniz. Bu örnekte, bir çağrının yapılıp yapılmadığını kontrol etmek ve hangi bağımsız değişkenlerle yapıldığını kontrol etmek için kullanılabilecek Vitest Mock Functions işlevi olan vi.fn() örneği kullanılmaktadır. Örneğimizde bu, önceki örnekte taklit edilen fetch ile etkileşim kurar ve test, geçirilen kimliğin "sam" olduğunu onaylayabilir:

  const userChosenFn = vi.fn();
  const ucForTest: ContextType<typeof UserContext> = { userChosen: userChosenFn as any };
  const c = render(
    <UserContext.Provider value={ucForTest}>
      <UserList />
    </UserContext.Provider>,
  );

  const chooseButton = await c.getByText(/Choose);
  chooseButton.click();
  assert.deepStrictEqual(userChosenFn.mock.calls, [['sam']]);

Bu, test etmeye çalıştığınız temel bileşendeki alakasız bağımlılıkları kaldırmanızı sağlayabilecek basit ama güçlü bir kalıptır.

Özet

Bu, test edilmesi zor bir React bileşenini test etmek ve korumak için bir bileşen testinin nasıl oluşturulacağını ve bileşenin, bağımlılıklarıyla (global fetch, içe aktarılan bir alt bileşen ve Context) doğru şekilde etkileşimde bulunduğundan emin olmaya odaklandığını gösteren hızlı ve basit bir örnektir.

Öğrendiklerinizi sınayın

React bileşenini test etmek için hangi yaklaşımlar kullanıldı?

Karmaşık bağımlılıkları test etmek için basit bağımlılıklarla alay etme
Bağlam kullanılarak bağımlılık ekleme
Saplama genelleri
Bir sayının artıp artmadığını kontrol etme