Strona główna / Artykuły

Testowanie jednostkowe w JavaScript za pomocą Jest i Vitest: praktyki, które się sprawdzają

Testy jednostkowe w JavaScriptie, które działają skutecznie: co należy do warstwy jednostkowej, Vitest vs Jest vs Mocha, podejście arrange-act-assert, mocki, przypadki krawędziowe, CI oraz w jakich sytuacjach pomaga sztuczna inteligencja.

Wydawca: REACTAPP.TOP7 min czytania1573 słów

Źródła: JavaScript Unit Testing Best Practices: A Practical Guide with Jest and Vitest Examples

Testy jednostkowe izolują najmniejsze elementy podlegające testowaniu — funkcje, metody, klasy — i sprawdzają, czy zachowują się prawidłowo. Stanowią one podstawę piramidy testowania, dlatego powinny odzwierciedlać rzeczywistą logikę biznesową i realne ryzyko, a nie tylko formalności.

Czerpiąc z powszechnych praktyk w branży, oto praktyczny przewodnik po solidnym testowaniu jednostkowym w JavaScript.

Czym charakteryzuje się dobry test jednostkowy?

Dobre testy jednostkowe sprawdzają wyniki, a nie prywatną implementację. Dzięki temu zestaw testów pozostaje stabilny podczas refaktoryzacji wewnętrznych elementów.

Cechy, które pojawiają się raz za razem:

  • Szybkie — w milisekundach. Powolne zestawy przestają działać przed zapisaniem zmian.
  • Izolowane — bez zależności od stanu poprzedniego testu; można je swobodnie modyfikować lub wykonywać równolegle.
  • Deterministyczne — zawsze dają ten sam wynik przy każdym uruchomieniu. Wynik „local-pass” / „CI-fail” oznacza niestabilność, a niestabilne testy niszczą zaufanie.
  • Lekki do odczytania — ten zestaw to dokumenty żywe. Nowicjusz powinien nauczyć się oczekiwanego zachowania wyłącznie na podstawie testów.
  • Przykład struktury:

    // A clean, predictable unit test using Jest/Vitest syntax
    describe('calculateTotalWithTax', () => {
      it('should apply a standard 10% tax rate to the subtotal', () => {
        // Arrange
        const subtotal = 100;
        const taxRate = 0.10;
    
        // Act
        const result = calculateTotalWithTax(subtotal, taxRate);
    
        // Assert
        expect(result).toBe(110);
      });
    
      it('should throw an explicit error if the subtotal is negative', () => {
        expect(() => calculateTotalWithTax(-50, 0.10)).toThrow('Invalid subtotal');
      });
    
      it('should return the subtotal unchanged when tax rate is 0', () => {
        // Edge case: no tax applied
        expect(calculateTotalWithTax(100, 0)).toBe(100);
      });
    
      it('should round to 2 decimal places to avoid floating-point errors', () => {
        // Guards against bugs like 19.99 * 1.10 = 21.989000000000004
        expect(calculateTotalWithTax(19.99, 0.10)).toBe(21.99);
      });
    });
    

    Co powinno tam być (a co nie)

    Zachowuj testy jednostkowe w czystej formie, aby czerwona barra oznaczała awarię logiki biznesowej. Wszystko inne należy umieścić wyżej w piramidzie.

    Struktura piramidy: wiele szybkich testów jednostkowych u dołu, mniej testów integracyjnych pośrodku oraz cienka warstwa E2E u góry. Jeśli ją odwrócisz, otrzymasz powolne, niestabilne i kosztowne zestawy.

    Czym powinny być testy jednostkowe

    Brak operacji wejścia/wyjścia, brak efektów ubocznych, brak zależności, którymi nie kontrolujesz:

    • Funkcje czyste (kalkulatory podatkowe, formatery, parserzy dat, mechanizmy zniżek)
    • Transformacje (map/filter/sort, API → view-model)
    • Zasady biznesowe (walidacja, poziomy cenowe, kryteria dostępności) — przypadki graniczne znajdują się tutaj, a testy jednostkowe stanowią tanie narzędzie do ich sprawdzania
    • Izolowany stan interfejsu użytkownika określony przez props/state, niezależny od otaczającego DOM czy działającego backendu

    Jeśli możesz powiedzieć „przy takich danych wejściowych oczekuję takiego wyniku” bez potrzeby sieci, systemu plików czy działającej usługi, to jest to test jednostkowy. Przeanalizuj wszystkie skrajne przypadki — cały sens polega na tym, że takie testy są szybkie.

    Czego unikać

    Zespoły marnują czas na testowanie jednostkowe niewłaściwych warstw:

    • Biblioteki third-party (Lodash sort, Axios headers) — nie jesteś ich systemem CI
    • Połączenia/zapytania do bazy danych — nawet wersja w pamięci SQLite stanowi test integracyjny
    • Złożone mechanizmy sieciowe (MSW + ponawiania prób + weryfikacja nagłówków) — to obszar testów integracyjnych
    • Cokolwiek, co wymaga Dockera, otwartych portów lub instrukcji „najpierw uruchom compose”

    Test weryfikacyjny: odłącz internet. Jeśli test się nie powiedzie, to nie jest test jednostkowy.

    Wybór frameworka JavaScript

    Dopasuj narzędzie do procesu budowania; optymalizuj pod kątem doświadczenia dewelopera, a nie tylko modnych rozwiązań. Proste ustawienia są lepsze niż gonienie za najnowszymi narzędziami.

    Dla większości nowoczesnych aplikacji wybór sprowadza się do Vitest kontra Jest.

    Vitest — silna domyślna opcja z Vite

    Zbudowany na bazie Vite, wykorzystuje jego konfigurację i obsługuje TypeScript oraz natywny ESM. Tryb obserwacji oraz wykonywanie testów są zazwyczaj szybsze niż w starszych narzędziach, a API podobne do Jestu ułatwia migrację.

    Jest — domyślne narzędzie w branży dla wielu pipeline’ów React

    W środowiskach Webpack, Next.js lub starszych stackach w stylu CRA, Jest stanowi bezpieczne rozwiązanie „wszystko w jednym”: tworzenie stwierdzeń, mockowanie, pomiar pokrycia kodu, robienie zrzutów ekranu. Jest jednak bardziej obciążający w dużych repozytoriach, a obsługa ESM jest mniej wygodna niż w Vitest, ale dojrzałość i ekosystem nadal przemawiają na jego korzyść dla wielu zespołów.

    Mocha — modułowe stacki

    Przydatny w backendach opartych na Node, gdy chcesz samodzielnie połączyć Chai i Sinon. Więcej konfiguracji, większa kontrola.

    Szybki wybór

    • Projekt Vite → Vitest
    • React z Webpackiem/Next/CRA → Jest
    • API Node wymagające modułowej architektury → Mocha

    Praktyki (w stylu Jest/Vitest)

    Celem są testy czytelne, deterministyczne i bezpieczne pod kątem refaktoryzacji.

    1. Arrange–Act–Assert

    Zawsze ta sama struktura:

    import { describe, it, expect } from 'vitest';
    import { add } from './math';
    
    describe('add()', () => {
      it('returns the sum of two numbers', () => {
        // Arrange
        const a = 2;
        const b = 3;
        // Act
        const result = add(a, b);
        // Assert
        expect(result).toBe(5);
      });
    });
    

    2. Nazwy opisujące zachowanie

    Zagnieżdżone funkcje describe / it w stylu BDD powinny być czytelne jak zdania:

    describe('CartService', () => {
      describe('when adding an item', () => {
        it('increases the total cart quantity by one', () => {
          // Test code...
        });
    
        it('updates the final checkout price', () => {
          // Test code...
        });
      });
    });
    

    Uruchamiający test wyświetla czytelne zdanie, które opisuje usługę, sytuację oraz oczekiwaną zmianę ilości.

    3. Wspólne pliki przygotowawcze, a nie rozproszone wartości literalne

    const testUser = {
      name: 'Alice',
      age: 30,
    };
    it('returns the user name', () => {
      expect(getUserName(testUser)).toBe('Alice');
    });
    

    Zmiany w danych przykładowych należy wprowadzać w jednym miejscu.

    4. Zachowanie zamiast implementacji

    Należy sprawdzać wyniki działania, a nie to, który pomocnik prywatny został uruchomiony. Testy powiązane z konkretną implementacją psują się podczas nieszkodliwych refaktoryzacji.

    5. Ostrożne symulowanie elementów zewnętrznych

    Stuby API, bazy danych i usług third-party dla szybkości i determinizmu. Rodzaje stubów:

    • Stuby kontrolują dane wejściowe
    • Śledzące obserwują interakcje
    • Mocki, gdy interakcja jest samym twierdzeniem
    • Fikcje jako małe, funkcjonalne zamienniki

    Użycie stuba dla funkcji fetch:

    import { vi } from 'vitest';
    
    global.fetch = vi.fn(() =>
      Promise.resolve({
        json: () => Promise.resolve({ name: 'Alice' }),
      })
    );
    

    Nie twórz mocków dla własnej logiki biznesowej, chyba że jest to konieczne — nadmierne tworzenie mocków ukrywa błędy.

    6. Przypadki krawędziowe i błędy

    Grenice, wartości null, puste łańcuchy, nieważne argumenty. Należy uwzględnić wartości minimalne/maksymalne oraz te nieco poza nimi, puste zbiory, brakujące pola, błędne typy, wartości ujemne oraz skrajne wartości całkowite.

    7. Niezależność

    Każdy porządek, dowolny harmonogram równoległy. Wspólne, zmienne pliki przygotowawcze powodują kaskady błędów. Lepiej używać beforeEach dla świeżego stanu.

    8. CI przy każdym PR

    Zapewnij uruchomienie zestawu testów jednostkowych w kontekście ciągłej integracji, aby regresje uniemożliwiały połączenie zmian, a pominięte testy nie mogły nic ukryć.

    Rola sztucznej inteligencji – i gdzie pozostają ludzie

    Sztuczne inteligencje typu LLM przyspieszają tworzenie standardowych, powtarzalnych wersji oraz szablonów próbnych. Podanie do modelu czystych funkcji i kryteriów akceptacji umożliwia stworzenie obszernych zestawów funkcji, ukrycie trudniejszych przypadków oraz użycie syntetycznych danych granicznych, których ludzie pomijają pod presją terminu.

    Modele nadal nie posiadają zdolności oceny architektonicznej ani specyfiki danej dziedziny. Skuteczny proces pracy: pozwól sztucznej inteligencji przygotować kompleksowe rozwiązania dla wszystkich przypadków, a następnie sprawdź każde twierdzenie w odniesieniu do rzeczywistych wymagań przed fuzją kodu. Ty jesteś tym, który decyduje o „poprawności”; model szybko pisze kod.

    Twoje własne doświadczenia z testowania jednostkowego jeszcze bardziej udoskonali ten proces – traktuj powyższe praktyki jako podstawę, a nie kompletną listę kontrolną.

    Budowanie podstawowego zestawu bez przytłoczenia

    Zacznij od czystych funkcji domeny: obliczeń finansowych, sprawdzania kwalifikacji, parserów. Pisz testy AAA z nazwami zachowań i wspólnymi ustawieniami. Dodawaj przypadki brzegowe, aż matryca „null / pusty / maksimum” stanie się nudna. Dopiero wtedy zajmij się komponentami interfejsu, i to tylko w odniesieniu do zachowań prop→render, które nie wymagają przeglądarki.

    Gdy test wymaga bazy danych lub Dockera, celowo przenieś go do folderu integracyjnego. Ścisłe trzymanie się struktury folderów jednostkowych zapewnia zgodność z zasadą, że czerwona barra jednostki oznacza awarię logiki.

    Jeśli chodzi o wybór frameworka, nie migruj z Jest na Vitest w trakcie pracy nad nowymi funkcjami tylko ze względu na mody. Jeśli już używasz Vite, Vitest jest naturalnym wyborem. Jeśli monorepo opiera się na Jest zgodnie z konwencjami Next.js, pozostań przy tym rozwiązaniu i inwestuj w przyspieszenie gdzie indziej (np. dzielenie CI, optymalizacja zrzutów stanu).

    Rytuały przeglądania, które zapewniają uczciwość zestawów testowych

    Podczas przeglądu PR odrzucaj testy, które sprawdzają liczbę wywołań metod prywatnych. Odrzucaj również wspólne, zmienne pliki przykładowe używane w różnych plikach. Przed połączeniem wymagaj, aby testy jednostkowe dawały wynik „zielony” w systemie CI. Okresowo usuwaj testy, które jedynie powtarzają zachowanie biblioteki do symulacji.

    Gdy używasz AI do tworzenia zestawów testowych, wklej funkcję czystą oraz kryteria akceptacji, a następnie usuń stwierdzenia, które zawierają wymagania wymyślone na poczekaniu. To ludzie nadal decydują, co oznacza „poprawność”; modele po prostu piszą szybciej.

    Nagroda na zakończenie

    Testy jednostkowe stanowią tanie zabezpieczenie, o ile pozostają szybkie, izolowane i skupione na wyniku. Chronij tę niszę. Pozwól testom integracyjnym i E2E zająć się resztą struktury, aby podstawa nigdy nie przekształciła się w miniaturę środowiska produkcyjnego.

    Katalog konkretnych przypadków krawędziowych do wykorzystania

    Dla API numerycznych: zero, jeden, maksymalna wartość całkowita, liczby ujemne, NaN w razie potrzeby oraz wartości przekraczające dokumentowany limit o jedną. Dla ciągów znakowych: pusty, zawierający tylko spacje, Unicode, niezwykle długi. Dla kolekcji: pusta, jednoelementowa, z duplikatami, już posortowana lub posortowana w odwrotnej kolejności. Dla obiektów: brak kluczy, pola nawiasowe typu null, nieoczekiwane dodatkowe klucze w przypadku ścisłego parsera.

    Napisz je raz jako przypadki oparte na tabeli, jeśli narzędzie do testowania to obsługuje. Taki katalog staje się częścią wiedzy zespołu i zapobiega zgłoszeniom typu „tylko ścieżka bez problemów”.

    Zdjęcia stanu z ostrożnością

    Zdjęcia stanu są w porządku dla stabilnego wyświetlania treści, ale niebezpieczne przy zmiennej strukturze markupu. W przypadku komponentów interaktywnych preferuj wyraźne twierdzenia dotyczące roli i tekstu za pomocą Testing Library. Jeśli nadal używasz zdjęć stanu, przeglądaj różnice tak jak kod produkcyjny – a nie jako zbędne elementy do szybkiego przejścia.

    Wskazówki dotyczące równoległych testów CI

    Dziel testy na pliki, natychmiast reaguj przy pierwszej nieudanej próbie testu jednostkowego i utrzymuj czas wykonywania tych testów poniżej kilku minut. Jeśli proces CI regularnie przekracza ten limit, albo masz zbyt wiele testów integracyjnych błędnie oznaczonych jako testy jednostkowe, albo w pliku setupFiles kryje się proces uruchamiania środowiska.

    Śledź osobno wskaźnik niestabilności testów. Suite, która jest uznawana za udaną dopiero po dwóch ponownych próbach, nie jest uznawana za udaną.

    Literatura pokrewna

  • Dwadzieścia pytań z interview o React-u, które rozróżniają użycie od zrozumienia — Virtual DOM, klucze, efekty, memoizacja, Context, SSR i hydratacja — wyjaśnione poprzez trudne kwestie, które faktycznie badają pytający, a nie definicje z podręczników.
  • Testy składników Reactu z Vitest i Testing Library — Połączenie Vitest z Vite, umieszczanie testów zachowania w tym samym miejscu, uruchamianie testów watch i CI oraz poinstruowanie agenta AI do realizacji cyklu weryfikacji typu czerwony-zielony.