Išmokite efektyviai naudoti `act` įrankį „React“ testavime, kad užtikrintumėte, jog jūsų komponentai veiktų kaip tikėtasi, ir išvengtumėte dažnų spąstų, tokių kaip asinchroniniai būsenos atnaujinimai.
„React“ testavimo įsisavinimas naudojant `act` įrankį: išsamus vadovas
Testavimas yra tvirtos ir prižiūrimos programinės įrangos pagrindas. „React“ ekosistemoje kruopštus testavimas yra gyvybiškai svarbus siekiant užtikrinti, kad jūsų komponentai veiktų kaip tikėtasi ir suteiktų patikimą vartotojo patirtį. `act` įrankis, pateikiamas `react-dom/test-utils`, yra esminis įrankis rašant patikimus „React“ testus, ypač kai susiduriama su asinchroniniais būsenos atnaujinimais ir šalutiniais poveikiais.
Kas yra `act` įrankis?
`act` įrankis yra funkcija, kuri paruošia „React“ komponentą patvirtinimams. Ji užtikrina, kad visi susiję atnaujinimai ir šalutiniai poveikiai buvo pritaikyti DOM, prieš pradedant daryti patvirtinimus. Galvokite apie tai kaip apie būdą sinchronizuoti savo testus su „React“ vidiniais būsenos ir atvaizdavimo procesais.
Iš esmės, `act` apgaubia bet kokį kodą, kuris sukelia „React“ būsenos atnaujinimus. Tai apima:
- Įvykių apdorojimo funkcijas (pvz., `onClick`, `onChange`)
- `useEffect` „kabliukus“ (hooks)
- `useState` nustatymo funkcijas
- Bet kokį kitą kodą, kuris keičia komponento būseną
Be `act`, jūsų testai gali atlikti patvirtinimus, kol „React“ dar nebus visiškai apdorojęs atnaujinimų, o tai lemia nepastovius ir nenuspėjamus rezultatus. Galite pamatyti įspėjimus, tokius kaip „An update to [component] inside a test was not wrapped in act(...).“ Šis įspėjimas rodo galimą lenktynių situaciją (race condition), kai jūsų testas atlieka patvirtinimus, kol „React“ dar nėra nuoseklioje būsenoje.
Kodėl `act` yra svarbus?
Pagrindinė `act` naudojimo priežastis yra užtikrinti, kad jūsų „React“ komponentai testavimo metu būtų nuoseklioje ir nuspėjamoje būsenoje. Jis sprendžia keletą dažnų problemų:
1. Asinchroninių būsenos atnaujinimo problemų prevencija
„React“ būsenos atnaujinimai dažnai yra asinchroniniai, o tai reiškia, kad jie neįvyksta iš karto. Kai iškviečiate `setState`, „React“ suplanuoja atnaujinimą, bet netaiko jo iš karto. Be `act`, jūsų testas gali patvirtinti reikšmę, kol būsenos atnaujinimas dar nebuvo apdorotas, o tai lemia neteisingus rezultatus.
Pavyzdys: neteisingas testas (be `act`)
import React, { useState } from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
test('increments the counter', () => {
render(<Counter />);
const incrementButton = screen.getByText('Increment');
fireEvent.click(incrementButton);
expect(screen.getByText('Count: 1')).toBeInTheDocument(); // This might fail!
});
Šiame pavyzdyje patvirtinimas `expect(screen.getByText('Count: 1')).toBeInTheDocument();` gali nepavykti, nes būsenos atnaujinimas, sukeltas `fireEvent.click`, dar nebuvo visiškai apdorotas, kai atliekamas patvirtinimas.
2. Užtikrinimas, kad visi šalutiniai poveikiai būtų apdoroti
`useEffect` „kabliukai“ (hooks) dažnai sukelia šalutinius poveikius, tokius kaip duomenų gavimas iš API arba tiesioginis DOM atnaujinimas. `act` užtikrina, kad šie šalutiniai poveikiai būtų užbaigti prieš testui tęsiantis, taip išvengiant lenktynių situacijų ir užtikrinant, kad jūsų komponentas veiktų kaip tikėtasi.
3. Testų patikimumo ir nuspėjamumo gerinimas
Sinchronizuodamas jūsų testus su „React“ vidiniais procesais, `act` padaro jūsų testus patikimesnius ir nuspėjamesnius. Tai sumažina nepastovių testų, kurie kartais pavyksta, o kartais ne, tikimybę, todėl jūsų testų rinkinys tampa patikimesnis.
Kaip naudoti `act` įrankį
`act` įrankį naudoti yra paprasta. Tiesiog apgaubkite bet kokį kodą, kuris sukelia „React“ būsenos atnaujinimus ar šalutinius poveikius, `act` iškvietimu.
Pavyzdys: teisingas testas (su `act`)
import React, { useState } from 'react';
import { render, screen, fireEvent, act } from '@testing-library/react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
test('increments the counter', async () => {
render(<Counter />);
const incrementButton = screen.getByText('Increment');
await act(async () => {
fireEvent.click(incrementButton);
});
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
Šiame ištaisytame pavyzdyje `fireEvent.click` iškvietimas yra apgaubtas `act` iškvietimu. Tai užtikrina, kad „React“ visiškai apdorojo būsenos atnaujinimą prieš atliekant patvirtinimą.
Asinchroninis `act`
`act` įrankį galima naudoti sinchroniškai arba asinchroniškai. Dirbant su asinchroniniu kodu (pvz., `useEffect` „kabliukais“, kurie gauna duomenis), turėtumėte naudoti asinchroninę `act` versiją.
Pavyzdys: asinchroninių šalutinių poveikių testavimas
import React, { useState, useEffect } from 'react';
import { render, screen, act } from '@testing-library/react';
async function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Fetched Data');
}, 50);
});
}
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
async function loadData() {
const result = await fetchData();
setData(result);
}
loadData();
}, []);
return <div>{data ? <p>{data}</p> : <p>Loading...</p>}</div>;
}
test('fetches data correctly', async () => {
render(<MyComponent />);
// Initial render shows "Loading..."
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Wait for the data to load and the component to update
await act(async () => {
// The fetchData function will resolve after 50ms, triggering a state update.
// The await here ensures we wait for act to complete all updates.
await new Promise(resolve => setTimeout(resolve, 0)); // A small delay to allow act to process.
});
// Assert that the data is displayed
expect(screen.getByText('Fetched Data')).toBeInTheDocument();
});
Šiame pavyzdyje `useEffect` „kabliukas“ gauna duomenis asinchroniškai. `act` iškvietimas naudojamas apgaubti asinchroninį kodą, užtikrinant, kad komponentas visiškai atsinaujino prieš atliekant patvirtinimą. `await new Promise` eilutė yra būtina, kad `act` turėtų laiko apdoroti atnaujinimą, kurį sukelia `setData` iškvietimas `useEffect` „kabliuko“ viduje, ypač aplinkose, kuriose planuoklis (scheduler) gali atidėti atnaujinimą.
Geriausios `act` naudojimo praktikos
Norėdami išnaudoti visas `act` įrankio galimybes, laikykitės šių geriausių praktikų:
1. Apgaubkite visus būsenos atnaujinimus
Užtikrinkite, kad visas kodas, sukeliantis „React“ būsenos atnaujinimus, būtų apgaubtas `act` iškvietimu. Tai apima įvykių apdorojimo funkcijas, `useEffect` „kabliukus“ ir `useState` nustatymo funkcijas.
2. Naudokite asinchroninį `act` asinchroniniam kodui
Dirbdami su asinchroniniu kodu, naudokite asinchroninę `act` versiją, kad užtikrintumėte, jog visi šalutiniai poveikiai būtų užbaigti prieš testui tęsiantis.
3. Venkite įdėtų `act` iškvietimų
Venkite `act` iškvietimų įdėjimo vienas į kitą. Įdėjimas gali sukelti netikėtą elgseną ir apsunkinti testų derinimą. Jei reikia atlikti kelis veiksmus, apgaubkite juos visus vienu `act` iškvietimu.
4. Naudokite `await` su asinchroniniu `act`
Naudodami asinchroninę `act` versiją, visada naudokite `await`, kad užtikrintumėte, jog `act` iškvietimas bus baigtas prieš testui tęsiantis. Tai ypač svarbu dirbant su asinchroniniais šalutiniais poveikiais.
5. Venkite perteklinio apgaubimo
Nors yra svarbu apgaubti būsenos atnaujinimus, venkite apgaubti kodą, kuris tiesiogiai nesukelia būsenos pokyčių ar šalutinių poveikių. Perteklinis apgaubimas gali padaryti jūsų testus sudėtingesnius ir mažiau skaitomus.
6. `flushMicrotasks` ir `advanceTimersByTime` supratimas
Tam tikrais atvejais, ypač dirbant su imituojamais laikmačiais (mocked timers) ar pažadais (promises), jums gali prireikti naudoti `act(() => jest.advanceTimersByTime(time))` arba `act(() => flushMicrotasks())`, kad priverstumėte „React“ nedelsiant apdoroti atnaujinimus. Tai yra pažangesnės technikos, tačiau jų supratimas gali būti naudingas sudėtingiems asinchroniniams scenarijams.
7. Apsvarstykite galimybę naudoti `userEvent` iš `@testing-library/user-event`
Vietoj `fireEvent`, apsvarstykite galimybę naudoti `userEvent` iš `@testing-library/user-event`. `userEvent` tiksliau imituoja realias vartotojo sąveikas, dažnai viduje tvarkydamas `act` iškvietimus, o tai lemia švaresnius ir patikimesnius testus. Pavyzdžiui:
import React, { useState } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
function MyComponent() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<input type="text" value={value} onChange={handleChange} />
);
}
test('updates the input value', async () => {
render(<MyComponent />);
const inputElement = screen.getByRole('textbox');
await userEvent.type(inputElement, 'hello');
expect(inputElement.value).toBe('hello');
});
Šiame pavyzdyje `userEvent.type` viduje tvarko reikiamus `act` iškvietimus, todėl testas tampa švaresnis ir lengviau skaitomas.
Dažniausi spąstai ir kaip jų išvengti
Nors `act` įrankis yra galingas, svarbu žinoti dažniausius spąstus ir kaip jų išvengti:
1. Pamiršimas apgaubti būsenos atnaujinimus
Dažniausias spąstas yra pamiršimas apgaubti būsenos atnaujinimus `act` iškvietimu. Tai gali lemti nepastovius testus ir nenuspėjamą elgseną. Visada patikrinkite, ar visas kodas, sukeliantis būsenos atnaujinimus, yra apgaubtas `act`.
2. Neteisingas asinchroninio `act` naudojimas
Naudojant asinchroninę `act` versiją, svarbu naudoti `await` `act` iškvietimui. To nepadarius, gali kilti lenktynių situacijos ir gauti neteisingi rezultatai.
3. Perteklinis pasikliovimas `setTimeout` ar `flushPromises`
Nors `setTimeout` ar `flushPromises` kartais gali būti naudojami sprendžiant problemas su asinchroniniais būsenos atnaujinimais, juos reikėtų naudoti saikingai. Daugeliu atvejų teisingas `act` naudojimas yra geriausias būdas užtikrinti, kad jūsų testai būtų patikimi.
4. Įspėjimų ignoravimas
Jei matote įspėjimą, pvz., „An update to [component] inside a test was not wrapped in act(...).“, neignoruokite jo! Šis įspėjimas rodo galimą lenktynių situaciją, kurią reikia išspręsti.
Pavyzdžiai įvairiose testavimo sistemose
`act` įrankis pirmiausia asocijuojasi su „React“ testavimo įrankiais, tačiau principai galioja nepriklausomai nuo konkrečios testavimo sistemos, kurią naudojate.
1. `act` naudojimas su „Jest“ ir „React Testing Library“
Tai yra labiausiai paplitęs scenarijus. „React Testing Library“ skatina naudoti `act`, kad būtų užtikrinti teisingi būsenos atnaujinimai.
import React from 'react';
import { render, screen, fireEvent, act } from '@testing-library/react';
// Component and test (as shown previously)
2. `act` naudojimas su „Enzyme“
„Enzyme“ yra dar viena populiari „React“ testavimo biblioteka, nors ji tampa retesnė, nes „React Testing Library“ populiarėja. Jūs vis dar galite naudoti `act` su „Enzyme“, kad užtikrintumėte teisingus būsenos atnaujinimus.
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
// Example component (e.g., Counter from previous examples)
it('increments the counter', () => {
const wrapper = mount(<Counter />);
const button = wrapper.find('button');
act(() => {
button.simulate('click');
});
wrapper.update(); // Force re-render
expect(wrapper.find('p').text()).toEqual('Count: 1');
});
Pastaba: Su „Enzyme“ jums gali prireikti iškviesti `wrapper.update()`, kad priverstumėte komponentą persirenderinti po `act` iškvietimo.
`act` skirtinguose globaliuose kontekstuose
`act` naudojimo principai yra universalūs, tačiau praktinis pritaikymas gali šiek tiek skirtis priklausomai nuo konkrečios aplinkos ir įrankių, kuriuos naudoja skirtingos kūrėjų komandos visame pasaulyje. Pavyzdžiui:
- Komandos, naudojančios „TypeScript“: `@types/react-dom` pateikiami tipai padeda užtikrinti, kad `act` būtų naudojamas teisingai, ir suteikia kompiliavimo metu atliekamą galimų problemų patikrinimą.
- Komandos, naudojančios CI/CD vamzdynus: Nuoseklus `act` naudojimas užtikrina, kad testai būtų patikimi ir išvengtų klaidingų gedimų CI/CD aplinkose, nepriklausomai nuo infrastruktūros teikėjo (pvz., „GitHub Actions“, „GitLab CI“, „Jenkins“).
- Komandos, dirbančios su internacionalizacija (i18n): Testuojant komponentus, kurie rodo lokalizuotą turinį, svarbu užtikrinti, kad `act` būtų teisingai naudojamas tvarkant bet kokius asinchroninius atnaujinimus ar šalutinius poveikius, susijusius su lokalizuotų eilučių įkėlimu ar atnaujinimu.
Išvada
`act` įrankis yra gyvybiškai svarbus rašant patikimus ir nuspėjamus „React“ testus. Užtikrindamas, kad jūsų testai būtų sinchronizuoti su „React“ vidiniais procesais, `act` padeda išvengti lenktynių situacijų ir užtikrina, kad jūsų komponentai veiktų kaip tikėtasi. Laikydamiesi šiame vadove aprašytų geriausių praktikų, galite įvaldyti `act` įrankį ir rašyti tvirtesnes bei lengviau prižiūrimas „React“ programas. Įspėjimų ignoravimas ir `act` nenaudojimas sukuria testų rinkinius, kurie meluoja kūrėjams ir suinteresuotosioms šalims, o tai lemia klaidas produkcinėje aplinkoje. Visada naudokite `act`, kad sukurtumėte patikimus testus.