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.
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.
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ı?