Uygulamada bileşen testi

Bileşen testi, pratik test kodunu göstermeye başlamak için iyi bir yerdir. Bileşen testleri, basit birim testlerinden daha kapsamlı, uçtan uca testten daha az karmaşıktır ve DOM ile etkileşimi gösterir. Daha felsefi bir bakış açısıyla, React'in kullanımı web geliştiricilerin web sitelerini veya web uygulamalarını bileşenlerden oluştuğu şekilde düşünmesini kolaylaştırdı.

Bu nedenle, ne kadar karmaşık olduklarına bakılmaksızın bağımsız bileşenleri test etmek, yeni veya mevcut bir uygulamayı test etme konusunda düşünmeye başlamak için iyi bir yöntemdir.

Bu sayfada, karmaşık harici bağımlılıkları olan küçük bir bileşenin test edilmesi adım adım açıklanmaktadır. Başka bir kodla etkileşime geçmeyen bir bileşeni test etmek kolaydır. Örneğin, bir düğmeyi tıklayıp bir sayının arttığını onaylayabilirsiniz. Gerçekte, çok az kod bu şekildedir ve etkileşim içermeyen kodu test etmek sınırlı değere sahip olabilir.

Test edilen bileşen

Bir React bileşenini test etmek için Vitest'i ve JSDOM ortamını kullanırız. Bu sayede, tarayıcı emülasyonu yaparken komut satırında Düğüm'ü kullanarak hızlı bir şekilde test yürütebiliriz.

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

UserList adlı bu React bileşeni, ağdan kullanıcı listesini getirir ve bunlardan birini seçmenize olanak tanır. Kullanıcı listesi, useEffect içinde fetch kullanılarak elde edilir ve seçim işleyici Context tarafından aktarılır. Kodu şudur:

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 örnekte React en iyi uygulamaları gösterilmemektedir (ör. useEffect içinde fetch kullanıyordur) ancak kod tabanınız muhtemelen bunun gibi birçok durum içerecektir. Daha da önemlisi, bu destek kayıtları ilk bakışta test edilmesi zor görünebilir. Bu kursun ilerideki bir bölümünde test edilebilir kod yazmaya ayrıntılı olarak değineceğiz.

Bu örnekte test ettiğimiz öğeler şunlardır:

  • Ağdaki verilere yanıt olarak doğru bir DOM oluşturulup oluşturulmadığını kontrol edin.
  • Bir kullanıcıyı tıklamanın geri aramayı tetiklediğini onaylayın.

Her bileşen farklıdır. Bu testi ilgi çekici kılan nedir?

  • Ağdan gerçek veriler istemek için genel fetch değerini kullanır. Bu değer, test sırasında kararsız veya yavaş olabilir.
  • UserRow adlı başka bir sınıfı içe aktarır. Bu sınıfı dolaylı olarak test etmek istemeyebiliriz.
  • Özellikle test edilen kodun parçası olmayan bir Context kullanır ve normalde bir üst bileşen tarafından sağlanır.

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

Bu bileşenle ilgili çok temel bir şeyi hızlıca test edebiliriz. Bu örneğin pek faydalı olmadığını belirtmek isteriz. Ancak, ortak metni UserList.test.tsx adlı bir benzer dosyasında oluşturmak yararlı olur (Vitest gibi test koşucularının varsayılan olarak .test.js veya benzeri ile biten dosyaları (.tsx dahil) çalıştırdığı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 doğrular. Bileşenin ağa fetch gönderme yan etkisi olmasına rağmen çalışır. fetch, testin sonunda bitiş noktası belirlenmeden hâlâ devam ediyor. Test sona erdiğinde herhangi bir kullanıcı bilgisinin gösterildiğini onaylayamazsınız (en azından zaman aşımı süresini beklemeden).

Örnek fetch()

Öykünme, gerçek bir işlevi veya sınıfı bir test için kontrolünüz altındaki bir öğeyle değiştirme işlemidir. Bu, en basit birim testleri hariç neredeyse tüm test türlerinde yaygın bir uygulamadır. Bu konu İddialar ve diğer primitifler bölümünde daha ayrıntılı olarak ele alınmıştır.

Testinizin "gerçek dünyadan" veya bilinmeyen verileri değil, hızlı bir şekilde tamamlanması ve beklediğiniz verileri döndürmesi için testinizde fetch() ile taklit edebilirsiniz. fetch global bir değişkendir. Yani kodumuza import veya require eklememiz gerekmez.

vitest'te, vi.fn() tarafından döndürülen özel bir nesneyle vi.stubGlobal'ü çağırarak bir globali taklit edebilirsiniz. Bu, daha sonra değiştirebileceğimiz bir taklit oluşturur. Bu yöntemler, bu kursun sonraki bir bölümünde daha ayrıntılı olarak incelenecek olsa da aşağıdaki kodda uygulamalı olarak 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 bir sürümünü tanımlar ve ardından bu sürümün görünmesini bekler. Metin görünmüyorsa (queryByText içindeki sorguyu yeni bir adla değiştirerek bunu kontrol edebilirsiniz) test başarısız olur.

Bu örnekte Vitest'in yerleşik alay yardımcıları kullanıldı ancak diğer test çerçeveleri alay etmeye benzer yaklaşımlara sahiptir. Vitest, tüm testlerden sonra vi.unstubAllGlobals() çağrısını yapmanız veya eşdeğer bir küresel seçenek ayarlamanız gerektiği açısından benzersizdir. Yaptığımız işi "geri almadığımızda" fetch taklit testi diğer testleri etkileyebilir ve her istek, garip JSON yığınımızla yanıtlanır.

Test amaçlı içe aktarma işlemleri

UserList bileşenimizin kendisinin UserRow adlı bir bileşeni içe aktardığını fark etmiş olabilirsiniz. Kodunu eklemedik ancak kullanıcının adını oluşturduğunu görebilirsiniz: Önceki test "Sam" değerini kontrol eder ve bu değer doğrudan UserList içinde oluşturulmaz. Bu nedenle UserRow kaynağından gelmelidir.

Kullanıcı adlarının bileşenimizde nasıl ilerlediğini gösteren bir akış şeması.
UserListTest, UserRow için görünürlüğe sahip değil.

Ancak UserRow karmaşık bir bileşen olabilir. Daha fazla kullanıcı verisi getirebilir veya testimizle alakalı olmayan yan etkileri olabilir. Özellikle test etmek istediğiniz bileşenler daha karmaşık hale geldiğinden ve bağımlılıklarıyla daha iç içe geçtiği için bu değişkeni kaldırmak testlerinizi daha faydalı hale getirir.

Neyse ki, testiniz bunları doğrudan kullanmasa bile belirli içe aktarma işlemlerini taklit etmek için Vitest'i kullanabilirsiniz. Böylece, bunları kullanan tüm kodlara basit veya bilinen bir sürüm sağlanır:

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

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

Bu, fetch ile alay etmek için global bir araç gibi güçlü bir araçtır. Ancak kodunuzda çok sayıda bağımlı öğe varsa sürdürülemez bir hale gelebilir. Yine de en iyi çözüm, test edilebilir kod yazmaktır.

Tıklayıp bağlam bilgisi sağlama

React ve Lit gibi diğer kitaplıklarda Context adlı bir kavram bulunur. Örnek kodda, kullanıcı seçilirse yöntemi çağıran UserContext yer alır. Bu, geri çağırma işlevinin doğrudan UserList'e iletildiği "öznitelik ayrıntılı inceleme"nin alternatifi olarak görülür.

Test aracımız UserContext sağlamadı. React testine bu olmadan bir tıklama işlemi eklemek, en kötü ihtimalle testi kilitleyebilir. Başka bir yerde varsayılan bir örnek sağlanırsa en iyi ihtimalle bu, kontrolümüz dışında bazı davranışlara neden olabilir (yukarıdaki bilinmeyen UserRow'e benzer).

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

Bunun yerine, bileşeni oluştururken kendi Context'inizi sağlayabilirsiniz. Bu örnekte, bir çağrının yapılıp yapılmadığını ve hangi bağımsız değişkenlerin kullanıldığını kontrol etmek için kullanılabilen Vitest Mock Function olan vi.fn() örneği kullanılmaktadır.

Bizim durumumuzda bu, önceki örnekte taklit edilen fetch ile etkileşime girer ve test, iletilen 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şenden alakasız bağımlılıkları kaldırmanıza olanak tanıyan basit ancak güçlü bir kalıptır.

Özet olarak

Bu örnekte, test edilmesi zor bir React bileşenini test etmek ve korumak için bileşen testinin nasıl oluşturulacağı gösterilmektedir. Bu test, bileşenin bağımlılıkları (fetch global, içe aktarılan bir alt bileşen ve bir Context) ile doğru şekilde etkileşime geçtiğinden emin olmaya odaklandı.

Öğrendiklerinizi test etme

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

Genel değişkenleri sabitleme
Bağlam kullanarak bağımlılık ekleme
Sayının artırılıp artırılmadığını kontrol etme
Test etmek için basit olanlarla karmaşık bağımlılıklarla alay etme