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