Testowanie
Po co testować?
Testy automatyczne pomagają Tobie i Twojemu zespołowi budować złożone aplikacje Vue szybko i z większą pewnością poprzez zapobieganie regresji i zachęcanie do dzielenia aplikacji na testowalne funkcje, moduły, klasy i komponenty. Jak z każdą aplikacją, Twoja aplikacja Vue może zepsuć się na wiele sposobów i ważnym jest by móc szybko te problemy zauważać i naprawiać przed release'ami.
W tym poradniku omówimy podstawową terminologię oraz damy Ci nasze rekomendacje co do tego, które narzędzia wybrać dla Twojej aplikacji Vue 3.
Istnieje również sekcja bardziej specyficzna dla Vue - skupiająca się na composables. Zobacz testowanie composables by dowiedzieć się więcej.
Kiedy testować
Zacznij testować jak najwcześniej! Zalecamy by pisać testy tak wcześnie jak to możliwe. Im dłużej zwlekasz z dodaniem testów do Twojej aplikacji tym większa ilość zależności znajdzie się w niej i tym trudniej będzie zacząć.
Typy testów
Podczas planowania strategii na testowanie swojej aplikacji Vue, warto rozważyć następujące typy testów:
- Jednostkowe (Unit tests): Sprawdzają czy dane wejściowe danej funkcji, klasy czy komponentu odpowiadają oczekiwanym danym wyjściowej lub oczekiwanym skutkom ubocznym.
- Komponentów: Sprawdzają czy Twoje komponenty montują się, renderują się, mogą zachodzić w interakcje i zachowują się zgodnie z oczekiwaniami. Testy te wymagają większej ilości kodu, są również bardziej złożone i wymagają większej ilości czasu na wykonanie.
- End-to-end (E2E): Sprawdzają czy funkcjonalności, które pokrywają wiele stron i wykonują faktyczne zapytania do serwerów związanych z Twoją aplikacją. Testy te wymagają postawienia bazy danych czy backendu.
Każdy typ testów odgrywa swoją rolę w strategii na testowanie Twojej aplikacji i każdy z nich zabezpiecza Cię przed różnymi typami problemów.
Przegląd
Poniżej krótko omówimy każdy z tych typów, jak je zaimplementować w aplikacjach Vue oraz wylistujemy ogólne rekomendacje.
Testy jednostkowe
Testy jednostkowe są pisane po to by zweryfikować czy małe, wyizolowane kawałki kodu działają zgodnie z oczekiwaniami. Test jednostkowy zazwyczaj pokrywa jedną funkcję, klasę, composable lub moduł. Testy jednostkowe skupiają się na poprawnej logice i mają wgląd jedynie w bardzo mały kawałek funkcjonalności danej aplikacji. Mogą mockować duże kawałki środowiska aplikacji (jak na przykład stan początkowy, skomplikowane moduły, zależności czy zapytania do serwerów i usług).
Na ogół, testy jednostkowe wyłapują problemy związane z logiką biznesową danej funkcji czy samą jej logiczną poprawnością.
Weźmy na przykład poniższą funkcję increment
:
js
// helpers.js
export function increment(current, max = 10) {
if (current < max) {
return current + 1
}
return current
}
Ponieważ jest ona bardzo niezależna, łatwo wywołać funkcję increment
i sprawdzić czy zwraca to, czego oczekujemy, a więc napiszemy dla niej test jednostkowy.
Jeśli któraś asercja nie powiedzie się, będzie to oznaczało jasny i wyraźny problem zlokalizowany w funkcji increment
.
js
// helpers.spec.js
import { increment } from './helpers'
describe('increment', () => {
test('zwiększa licznik o 1', () => {
expect(increment(0, 10)).toBe(1)
})
test('nie zwiększa obecnej wartości powyżej maksimum', () => {
expect(increment(10, 10)).toBe(10)
})
test('ma domyślne maksimum równe 10', () => {
expect(increment(10)).toBe(10)
})
})
Jak mówiliśmy wcześniej, testy jednostkowe są zazwyczaj aplikowane do bardzo niezależnych kawałków logiki biznesowej, komponentów, klas, modułów lub funkcji, które nie mają styczności z renderowaniem UI, zapytaniami do serwerów czy innymi zależnościami związanymi ze środowiskiem.
Są to kawałki kodu najczęściej pisane w prostym JavaScript lub TypeScript, niezależne od samego Vue. W praktyce, pisanie testów jednostkowych dla logiki biznesowej w Vue nie różni się znacząco od aplikacji pisanych w innych frameworkach.
Istnieją dwa przypadki w których testujemy funkcjonalności mocno powiązane z samym Vue:
- Composables
- Komponenty
Composables
Pierwszą z grup funkcji specyficznych dla aplikacji Vue są Composables, które wymagają innego podejścia podczas pisania testów. Zobacz Testowanie Composables poniżej, aby dowiedzieć się więcej.
Testy jednostkowe komponentów
Komponent może być przetestowany na dwa sposoby:
Testy białej skrzynki (Whitebox tests)
Testy "białej skrzynki" są pisane ze świadomością szczegółów implementacji i zależności komponentu. Skupiają się na wyizolowaniu komponentu, który testujemy. Testy te zazwyczaj mockują niektóre, lub nawet wszystkie dzieci komponentu, setupując również wszelkie pluginy czy zależności (np. Pinia).
Testy czarnej skrzynki (Blackbox tests)
Testy "czarnej skrzynki" pisane są bez ingerowania w szczegóły implementacji komponentu. Mockują one możliwie minimum, aby przetestować pełną integrację komponentu w ramach całego systemu. Zazwyczaj renderują również wszystkie dzieci komponentu i mogą być rozważane bardziej jako "testy integracyjne". Zobacz nasze zalecenia testowania komponentów poniżej.
Rekomendacje
Oficjalne, rekomendowane konfigurowanie nowych projektów przez
create-vue
korzysta z Vite, w związku z czym zalecamy również framework do testów jednostkowych, który wykorzystuje tę samą konfigurację i pipeline transformacji kodu bezpośrednio z Vite. Vitest jest frameworkiem testów jednostkowych zaprojektowanym dokładnie w tym celu, został on utworzony i jest wspierany przez członków zespołów Vue i Vite. Integruje się z projektami Vite z minimum potrzebnej konfiguracji oraz jest on bardzo szybki.
Inne opcje
- Jest jest popularnym frameworkiem testów jednostkowych. Jednakże, zalecamy używać Jesta jedynie w przypadku gdy masz już istniejące testy napisane w Jeście, które planujesz zmigrować do projektu używającego Vite, w związku z tym, że Vitest oferuje o wiele prostszą integrację i znacznie lepszy performance.
Testowanie komponentów
Komponenty są głównymi klockami z których budujemy interfejsy w aplikacjach Vue. Naturalnym jest więc, aby testować komponenty w izolacji podczas testowania zachowania Twojej aplikacji. Z perspektywy granularności, testy komponentów są nieco ponad testami jednostkowymi i można uznać je za pewną formę testów integracyjnych. Większość Twojej aplikacji może być pokryta testami komponentów i rekomendujemy, aby każdy komponent Vue posiadał swój odrębny plik z testami.
Testy komponentów służą wyłapywaniu problemów związanych z jego propsami, emitowanymi zdarzeniami, slotami jakie dostarcza, stylami, klasami, hookami cyklu życia i innymi.
Testy komponentów nie powinny mockować dzieci tego komponentu, a jedynie interakcje między nimi w taki sposób, w jaki robiłby to użytkownik. Na przykład, test komponentu powinien kliknąć na element tak jak użytkownik zamiast wywoływać interakcję w sposób programowy.
Testy komponentów powinny skupiać się na ich publicznych interfejsach niż szczegółach implementacji. Dla większości komponentów, publiczny interfejs ogranicza się do: emitowanych zdarzeń, propsów oraz slotów. Podczas pisania testów pamiętaj by testować to co komponent robi, nie to jak to robi.
ZALECENIA
Dla logiki wizualnej: sprawdzaj prawidłowo wyrenderowany output na podstawie propsów i slotów.
Dla logiki zachowania: sprawdzaj prawidłowe aktualizacje treści czy emitowane zdarzenia na podstawie interakcji przychodzący od strony użytkownika.
W poniższym przykładzie, demonstrujemy przykład testów komponenta "Licznik", który ma element DOM oznaczony jako "inkrementuj", który możemy kliknąć. Przekazujemy propa
max
określającego maksymalną wartość licznika równą2
, a więc gdy klikniemy w przycisk 3 razy, interfejs powinien pokazywać2
.Nie znamy szczegółowej implementacji Licznika, jedynie że możemy przekazać do niego propa
max
oraz "wyjście" jakim jest stan drzewa DOM w taki sposób jaki widzi go użytkownik.
Vue Test Utils
Cypress
Testing Library
js
const valueSelector = '[data-testid=stepper-value]'
const buttonSelector = '[data-testid=increment]'
const wrapper = mount(Stepper, {
props: {
max: 1
}
})
expect(wrapper.find(valueSelector).text()).toContain('0')
await wrapper.find(buttonSelector).trigger('click')
expect(wrapper.find(valueSelector).text()).toContain('1')
PRZECIWWSKAZANIA
Nie sprawdzaj wewnętrznego, prywatnego stanu komponentu czy też jego metod. Testowanie szczegółów implementacyjnych nie jest dobrym pomysłem, gdyż te znacznie częściej ulegają zmianom (które również musimy potem wprowadzać w testach).
Fundamentalnym działaniem komponentu, jest wyrenderowanie odpowiednich treści w drzewie DOM, a więc testowanie, które sprawdza DOM dostarcza taki sam stopień pewności co do poprawnego działania (o ile nie większy) podczas gdy dostarcza znacznie większą odporność na zmiany implementacji komponentu.
Nie polegaj wyłącznie na testach z wykorzystaniem snapshotów. Asercje dotyczące ciągów znaków HTML nie opisują poprawności. Pisz testy z wyraźnie zdeklarowanymi celami.
Jeśli dana metoda musi być dokładnie przetestowana, warto rozważyć wyodrębnienie jej do samodzielnej, reużywalnej funkcji, z własnymi dedykowanymi testami jednostkowymi. Jeśli nie może być łatwo wyodrębniona, może być przetestowana jako część testu komponentu, integracyjnego lub end-to-end, który ją pokryje.
Rekomendacje
Vitest dla komponentów lub composables, które renderujemy headlessowo (np. funkcja
useFavicon
w VueUse). Komponenty i DOM mogą być testowane przy użyciu@vue/test-utils
.Testy komponentów w Cypress dla komponentów, których zachowanie polega na prawidłowym renderowaniu styli, lub wywoływaniu zdarzeń natywnych DOM. Może być również używany razem z Testing Library poprzez @testing-library/cypress.
Główną różnicą między Vitestem a narzędziami wykonującymi testy przy użyciu przeglądarki są szybkość i kontekst wykonania. W skrócie, narzędzia oparte o przeglądarkę jak Cyppress, mogą wyłapać problemy których narzędzia oparte o node, jak Vitest, nie potrafią (np. problemy związane ze stylem, prawdziwymi, natywnymi zdarzeniami w DOM, cookies, local storage czy problemami z połączeniem do sieci), ale narzędzia te są rzędy wielkości wolniejsze niż Vitest ponieważ otwierają faktyczną przeglądarkę, kompilują style i więcej. Cypress jest narzędziem opartym o przeglądarkę, które wspiera testy komponentów. Zachęcamy do przeczytania o porównaniu Vitesta do innych narzędzi w tym również Cypressa.
Biblioteki do montowania
Testowanie komponentów często wymaga również montowania komponentu, który będzie testowany w izolacji, wyzwalania symulowanych akcji użytkownika czy dokonywanie asercji na wyjściowym stanie DOM. Istnieją dedykowane biblioteki które znacząco ułatwiają te zadania.
@vue/test-utils
to oficjalna biblioteka niskiego poziomu do testowania komponentów, która napisana była, aby dostarczyć użytkownikom dostęp do specyficznych API Vue. W oparciu o tą bibliotekę napisana została biblioteka@testing-library/vue
.@testing-library/vue
to biblioteka do testów Vue, skupiona wokół testowania komponentów bez potrzeby znania szczegółów implementacji. Jej fundamentalnym założeniem jest to, że im więcej testów napiszemy w sposób przypominający to w jaki korzystamy z implementowanych funkcjonalności, tym większą pewność będą one dawać co do poprawnego ich działania.
Zalecamy używanie @vue/test-utils
do testów komponentów w aplikacjach. @testing-library/vue
nie radzi sobie niestety z testami asynchronicznych komponentów korzystających z Suspense, powinna być więc używana ostrożnie.
Inne opcje
Nightwatch to narzędzie do testów E2E ze wsparciem do testów komponentów Vue. (Przykładowy projekt)
WebdriverIO to narzędzie do testów komponentów w różnych przeglądarkach, które polega na natywnych interakcjach użytkownika, opartych o standardową automatyzację. Może być również używane razem z Testing Library.
Testy E2E
Podczas gdy testy jednostkowe dostarczają nam pewien poziom pewności poprawnego działania, testy jednostkowe i komponentów są ograniczone w swoich możliwościach do jedynie do pokrycia holistycznego aplikacji gdy wypuszczamy ją na produkcję. Skutkiem tego, testy end-to-end (E2E) mają na celu pokrycie w pewnym sensie najważniejszego aspektu aplikacji: co się dzieje gdy użytkownicy faktycznie używają Twojej aplikacji.
Testy end-to-end skupiają się wokół zachowania użytkownika na przekroju wielu stron, wykonują zapytania na faktycznej aplikacji Vue zbudowanej pod cele produkcyjne. Wymagają one zatem postawienia właściwej bazy danych czy usług backendowych i mogą być wywołane na aplikacji żyjącej na środowisku stagingowym.
Testy te mogą często wyłapać błędy związane z routerem, biblioteką zarządzania stanem, komponentami najwyższego poziomu (np. App czy Layout), publicznymi assetami czy obsługą zapytań sieciowych. Jak nadmieniliśmy wyżej wykrywają one krytyczne problemy, które mogą być niemożliwe do wychwycenia przez testy jednostkowe czy komponentowe.
Testy end-to-end nie importują w ogóle kodu Twojej aplikacji Vue, a jedynie opierają się kompletnie na testowaniu aplikacji poprzez odwiedzanie stron w rzeczywistej przeglądarce.
Testy end-to-end sprawdzają poprawność wielu wartsw aplikacji na raz. Mogą celować w aplikację zbudowaną lokalnie, czy nawet aplikację na środowisku stagingowym. Testowanie aplikacji na środowisku stagingowym testuje nie tylko kod frontendowy czy serwer statyczny ale też również całą infrastrukturę i usługi backendowe.
Im więcej testów napiszemy w sposób przypominający to w jaki korzystamy z implementowanych funkcjonalności, tym większą pewność będą one dawać co do poprawnego ich działania. - Kent C. Dodds - Autor biblioteki Testing Library
Testy E2E, sprawdzające jak akcje użytkownika wpływają na aplikację są często kluczowe by mieć wysoką pewność co do tego czy nasza aplikacja działa poprawnie lub nie.
Wybór rozwiązania do testów E2E
Testy end-to-end (E2E) zbudowały z czasem nieco negatywną reputację z powodu zawodzących testów i spowalniania procesu deweloperskiego, jednakże współczesne narzędzia do testów E2E znacząco zaadresowały te problemy i dostarczają znacznie bardziej niezawodne, interaktywne i pomocne testy. Poniższe sekcje mają na celu nakreślić czynniki, które warto mieć na uwadze podczas wyboru narzędzi do testów E2E dla Twojej aplikacji.
Testowanie cross-browser
Jedną z głównych zalet, z których znane są testy end-to-end (E2E) jest możliwość spsrawdzenia jak działa Twoja aplikacja na różnych przeglądarkach. Pomimo, że możemy chcieć mieć 100% pokrycie sprawdzenia jak aplikacja działa na różnych przeglądarkach, istotne jest mieć świadomość, że korzyści maleją wraz ze wzrostem kosztów zasobów i czasu koniecznego by wykonywać te testy. Ważnym jest więc mieć rozwagę i świadomość kosztów związanych z ilością testów między różnymi przeglądarkami.
Sybsza informacja zwrotna
Głównym problemem testów end-to-end (E2E) jest fakt, że wykonanie pełnego zestawu testów zajmuje dużo czasu. W typowym przypadku, dzieje się to jedynie podczas procesu continuous integration oraz deploymentu (CI/CD). Współczesne frameworki testów E2E pomagają rozwiązać ten problem poprzez funkcjonalności takie jak paralelizacja, która pozwala pipeline'om CI/CD działać rzędy wielkości szybciej. W dodatku, podczas pracy lokalnej, możliwośc uruchomienia jednego konkretnego testu dla strony nad którą obecnie pracujemy przy jednoczesnym wsparciu hot reloadingu tych testów znacznie usprawniają rytm pracy i produktywność deweloperów.
Pierwszorzędne doświadczenie debugowania
Podczas gdy zazwyczaj deweloperzy przeszukiwali logi w oknie terminala tekstowego celem analizy co poszło nie tak podczas wykonywania testów, współczesne frameworki testów end-to-end (E2E) pozwalają deweloperom wykorzystać narzędzia z którymi są już zaznajomieni jak np. narzędzia deweloperskie przeglądarek.
Podgląd w trybie headless
Podczas gdy testy end-to-end (E2E) wykonywane są w ramach pipeline'ów continuous integration czy deploymentu, zazwyczaj wykonywane są w przeglądarkach typu headless (tj. nie ma widocznej przeglądarki którą użytkownik mógłby widzieć). Krytyczną funkcjonalnością współczesnych frameworków E2E jest możliwość zobaczenia snapshotów i/lub filmów wideo aplikacji podczas testów, dostarczając konieczny kontekst i informacje by zrozumieć czemu wykrywamy błędy. W przeszłości, utrzymywanie tych integracji było bardzo uciążliwe.
Rekomendacje
Biorąc wszystko pod uwagę, uważamy, że Cypress dostarcza najbardziej pełne rozwiązanie do testów E2E z funkcjonalnościami takimi jak pełen przydatnych informacji interfejs graficzny, świetne wsparcie w debugowaniu, wbudowane asercje, stuby, flake-resistence, paralelizacja i snapshoty. Jak wspominaliśmy wyżej, dostarcza wsparcie do testów komponentów. Wspiera przeglądarki oparte o Chromium, Firefoxa czy Electron. Support WebKita jest również dostępny, ale wymieniony jako eksperymentalny.
Inne opcje
Playwright jest również dobrym rozwiązaniem do testów E2E, które wspiera wszystkie współczesne silniki renderowania wliczając Chromium, WebKit czy Firefoxa. Umożliwia testy na Windowsie, Linuxie, macOS, lokalnie czy też w ramach CI, headlessowo lub nie oraz z natywną emulacją mobilną Google Chrome dla Androida i Safari w wersji mobilnej.
Nightwatch to rozwiązanie do testów E2E oparte o Selenium WebDriver. Daje to mu najszersze wsparcie przeglądarek, wliczając również natywne testy mobilne. Rozwiązania oparte o Selenium będą jednak wolniejsze niż Playwright czy Cypress.
WebdriverIO to framework automatyzacji testów dla webu i urządzeń mobilnych opartym o protokół WebDriver.
Przepisy
Dodawanie Vitest do projektu
W projekcie Vue opartym o Vite uruchom:
sh
> npm install -D vitest happy-dom @testing-library/vue
Następnie, zmodyfikuj konfigurację Vite dodając block opcji test
:
js
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
// ...
test: {
// włącz globalne API testów podobnie jak jest
globals: true,
// symuluj DOM przy użyciu happy-dom
// (wymaga instalacji happy-dom jako peer dependency)
environment: 'happy-dom'
}
})
TIP
Jeśli używasz TypeScript, dodaj vitest/globals
do pola types
w Twoim tsconfig.json
.
json
// tsconfig.json
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
Następnie, utwórz plik z nazwą kończącą się na *.test.js
w Twoim projekcie. Wszystkie pliki testów możesz umieścić w folderze test w głównym folderze projektu lub w folderach z testami obok plików z kodem źródłowym. Vitest automatycznie odnajdzie te testy po konwencji nazewnictwa.
js
// MyComponent.test.js
import { render } from '@testing-library/vue'
import MyComponent from './MyComponent.vue'
test('powinien działać', () => {
const { getByText } = render(MyComponent, {
props: {
/* ... */
}
})
// assert output
getByText('...')
})
Na koniec, zaktualizuj package.json
dodając skrypt do testów i uruchom go:
json
{
// ...
"scripts": {
"test": "vitest"
}
}
sh
> npm test
Testowanie Composables
Ta sekcja zakłada wcześniejsze zaznajomienie się z działem dedykowanym composables.
Rozważając testowanie composables, możemy podzielić je na dwie kategorie: composables które nie wymagają hostującej instancji komponentu, oraz composables, które tego nie potrzebują.
Composable wymaga hostującej instancji komponentu, gdy używa następujących API:
- Hooków cyklu życia
- Provide / Inject
Jeśli composable używa jedynie API reaktywności to możemy je testować bezpośrednio i wykonywać asercje na podstawie zwróconego stanu i metod:
js
// counter.js
import { ref } from 'vue'
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
return {
count,
increment
}
}
js
// counter.test.js
import { useCounter } from './counter.js'
test('useCounter', () => {
const { count, increment } = useCounter()
expect(count.value).toBe(0)
increment()
expect(count.value).toBe(1)
})
Composable który polega na hookach cyklu życia lub Provide / Inject musi być owrapowany hostującym komponentem, aby być poprawnie przetestowanym. Możemy utworzyć helpera jak poniżej:
js
// test-utils.js
import { createApp } from 'vue'
export function withSetup(composable) {
let result
const app = createApp({
setup() {
result = composable()
// pomiń ostrzeżenia o braku template
return () => {}
}
})
app.mount(document.createElement('div'))
// zwróć resultat i instancję aplikacji
// aby testować provide/unmount
return [result, app]
}
js
import { withSetup } from './test-utils'
import { useFoo } from './foo'
test('useFoo', () => {
const [result, app] = withSetup(() => useFoo(123))
// zmockowany provide by testować wstrzyknięcia
app.provide(...)
// wykonujemy asercje
expect(result.foo.value).toBe(1)
// wywołujemy hook onMounted jeśli potrzeba
app.unmount()
})
Dla bardziej złożonych composables, może być łatwiejsze napisać testy dla komponentu wrapującego używając technik z testowania komponentów.