Strona główna / Artykuły

Antypaternale w testowaniu z React, które dają fałszywe poczucie pewności w 2026 roku

Dowiedz się o dziesięciu powszechnych błędach w testowaniu w React – od niewłaściwego używania fireEvent po pułapki związane z mockowaniem sieci – które pozwalają na przeoczenie nieprawidłowych funkcjonalności mimo tego, że cała seria testów wykazuje się jako pomyślna.

Wydawca: REACTAPP.TOP14 min czytania3125 słów

Źródła: 10 React Testing Mistakes That Are Secretly Making Your Tests Useless in 2026

Pakiet testów pokazuje pełne przejście wszystkich testów, każda kontrola jest zielona, więc możesz spokojnie wdrożyć aplikację do produkcji. Następnie kolejka wsparcia się zapełnia: przycisk zakupu bezgłośnie przestaje działać na urządzeniach mobilnych, ekran logowania wyświetla błąd, gdy API odpowiada powoli, a osoba korzystająca z klawiatury w ogóle nie może korzystać z menu nawigacyjnego. Żaden z testów tego nie wykrył. Co więc się stało?

Do 2026 roku, dzięki używaniu React 19 w połączeniu z React Compiler i Testing Library v16, różnica pomiędzy pakietem testów, który jedynie pokazuje przejście wszystkich testów, a takim, który naprawdę chroni aplikację, stała się większa niż kiedykolwiek. Poniżej znajduje się dziesięć nawyków, które sprawiają wrażenie dokładnych testów, podczas gdy aplikacja w produkcji i tak potajemnie przestaje działać.

1. Testowanie szczegółów implementacji zamiast zachowań użytkowników

To klasyczny błąd w testowaniu React, który nadal jest popełniany bez przerwy. Komponent zostaje wyrenderowany, jego wewnętrzne elementy są badań, a sprawdzenia polegają na określeniu, czy useState został wywołany lub czy dana właściwość została przekazana. To nie sprawdza tego, co faktycznie doświadcza użytkownik — to sprawdza tylko to, w jaki sposób programista go zbudował.

// ❌ Useless: Testing internals
import { render } from '@testing-library/react';
import { LoginForm } from './LoginForm';
test('sets isSubmitting to true on submit', () => {
  const wrapper = render(<LoginForm />);
  const instance = wrapper.container.firstChild; // Reaching into internals

  // This tests React, not your app
  expect(instance).toHaveProperty('isSubmitting');
});

Taki test jest kruchy i bezsensowny: wystarczy przemienić nazwę zmiennej, zastąpić useState przez useReducer lub wydzielić logikę do osobnego hooka, a test przestaje działać — mimo że nic się nie zmieniło w rzeczywistym procesie logowania. Nie daje żadnych informacji na temat tego, czy ktoś może się zalogować.

// ✅ Useful: Testing behavior
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('submits the form with user credentials', async () => {
  const handleSubmit = vi.fn();
  render(<LoginForm onSubmit={handleSubmit} />);

  const user = userEvent.setup();

  await user.type(screen.getByLabelText(/email/i), 'test@example.com');
  await user.type(screen.getByLabelText(/password/i), 'secret123');
  await user.click(screen.getByRole('button', { name: /sign in/i }));

  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'secret123',
  });
});

Podstawowa zasada Testowania Biblioteki pozostaje aktualna również w 2026 roku: testy odzwierciedlające rzeczywiste użycie dają większe poczucie pewności. Jeśli nieszkodliwa refaktoryzacja może zepsuć twój test, to sprawdzasz coś na niewłaściwym poziomie.

2. Używanie fireEvent zamiast userEvent

fireEvent po prostu wyzwalają surowe zdarzenia DOM. userEvent odtwarza cały łańcuch działań wykonywanych przez prawdziwą osobę. Prawie nie ma już uzasadnienia dla używania fireEvent w testach komponentów, ale nadal się to stosuje, ponieważ tak uczyły starsze przewodniki.

// ❌ Useless: fireEvent does not simulate real interaction
test('toggles the menu', () => {
  render(<Navigation />);
  const button = screen.getByText('Menu');

  fireEvent.click(button); // Just fires a click event

  expect(screen.getByText('Dashboard')).toBeInTheDocument();
});

Problem polega na tym, że fireEvent.click nigdy nie potwierdza, czy element może faktycznie zostać kliknięty przez człowieka. Może być wyłączony, przykryty innym elementem lub całkowicie niedostępny za pomocą klawiatury. Sprawdzenie się udaje, mimo że funkcjonalność w rzeczywistości jest niewykorzystywalna.

// ✅ Useful: userEvent validates the interaction
test('toggles the menu with keyboard and mouse', async () => {
  render(<Navigation />);
  const user = userEvent.setup();
  const button = screen.getByRole('button', { name: /menu/i });

  // Verifies element is focusable and clickable
  await user.click(button);
  expect(screen.getByRole('link', { name: /dashboard/i })).toBeVisible();

  // Tests keyboard accessibility
  await user.keyboard('{Escape}');
  expect(screen.queryByRole('link', { name: /dashboard/i })).not.toBeInTheDocument();
});

userEvent najpierw sprawdza, czy cel nie jest wyłączony ani tylko do odczytu oraz czy rzeczywiście jest widoczny, zanim cokolwiek uruchomi, a następnie symuluje realistyczną sekwencję zdarzeń dotyczących wskaźnika, klawiatury i fokusa. Dostępność nie jest już tylko dodatkowym atutem – twoje testy powinny automatycznie to sprawdzać.

3. Symulowanie wywołań sieciowych na niewłaściwym poziomie

Załóżmy, że testujesz komponent, który pobiera dane z API. Szybkim rozwiązaniem jest stworzenie sztucznych implementacji funkcji fetch lub axios.get bezpośrednio w pliku testowym. Testy przechodzą pomyślnie. Następnie zespół backendu zmienia nazwę pola z userName na username, wtedy produkcja przestaje działać, a twój zestaw testów niczego nie zauważa.

// ❌ Useless: Mocking fetch directly
test('displays user profile', async () => {
  global.fetch = vi.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ userName: 'Alice' }), // Drifts from reality
    })
  );

  render(<UserProfile userId="123" />);

  expect(await screen.findByText('Alice')).toBeInTheDocument();
});

Problem polega na tym, że walidujesz dane na podstawie wymyślonej API, a nie rzeczywistej. Ten stub nie sprawdza nagłówków, nie weryfikuje struktury URL, ani nie symuluje opóźnień czy błędów. Daje fałszywe poczucie pewności, podczas gdy rzeczywista integracja stopniowo traci synchronizację.

// ✅ Useful: Mocking at the network layer with MSW
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
  http.get('/api/users/:id', ({ params }) => {
    return HttpResponse.json({ username: 'Alice', id: params.id });
  })
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('displays user profile from API', async () => {
  render(<UserProfile userId="123" />);

  // Tests the actual component + network layer together
  expect(await screen.findByText(/alice/i)).toBeInTheDocument();
  expect(screen.getByText(/id: 123/i)).toBeInTheDocument();
});

MSW interweniuje na granicy sieci, więc twój komponent wykonywa dokładnie tę samą ścieżkę wywołania fetch, jaką używałby w środowisku produkcyjnym. Gdy zmienia się kontrakt API, wystarczy poprawić obsługę w MSW w jednym miejscu, a każdy test przetestowujący ten endpoint automatycznie odzwierciedli nową rzeczywistość. Do 2026 roku MSW 2.x stał się standardem, a bezpośrednie tworzenie stubów dla fetch stanowi w istocie narastający dług techniczny.

4. Poleganie na zrzutach stanu jako zamienniku testów regresji

Testy snapshot są kuszące właśnie dlatego, że wymagają od nas niewiele. Wyświetlamy coś, zapisujemy powstały plik HTML i go przesyłamy. Kilka miesięcy później projektant zmienia moduł CSS, snapshot przestaje działać, a ktoś zatwierdza nowy snapshot jednym naciśnięciem klawisza, w ogóle nie czytając tego, co się zmieniło.

// ❌ Useless: Snapshot as a crutch
test('renders correctly', () => {
  const { container } = render(<Card title="Hello" />);
  expect(container.firstChild).toMatchSnapshot();
});

Problem polega na tym, że nikt tak naprawdę nie sprawdza 400-wierszowego raportu różnic w zserializowanym kodzie. Snapshot wskazuje, że coś się zmieniło, ale nie może powiedzieć, czy była to nieszkodliwa modyfikacja projektu, czy zepsuty układ. To hałas udający sieć bezpieczeństwa.

// ✅ Useful: Targeted assertions
test('renders card with accessible title and action', () => {
  render(<Card title="Hello" onAction={() => {}} />);

  expect(screen.getByRole('heading', { name: /hello/i })).toBeInTheDocument();
  expect(screen.getByRole('button', { name: /action/i })).toBeEnabled();
  expect(screen.getByRole('article')).toHaveAttribute('aria-labelledby');
});

Używanie precyzyjnych stwierdzeń sprawdzających pokazuje dokładnie, jakie zachowanie obiecuje komponent. Jeśli struktura markup się zmieni, ale podstawowe zasady dostępności nadal obowiązują, test nadal przepada pomyślnie. Jeśli rola nagłówka zostanie przypadkowo usunięta, otrzymujemy dokładną wiadomość o błędzie wskazującą na prawdziwy problem. Wchodząc w 2026 rok, warto całkowicie usunąć pliki snapshot i zastąpić je stwierdzeniami sprawdzającymi, które opisują nasze rzeczywiste oczekiwania.

5. Wybieranie elementów wyłącznie za pomocą data-testid

Umieszczanie atrybutu data-testid we wszystkich elementach wydaje się bezpieczną i niezawodną opcją — zapytanie zawsze znajduje swój cel. Jednak selektor oparty na data-testid nic nie potwierdza co do dostępności, semantyki ani tego, jak rzeczywisty użytkownik doświadcza interfejsu. Potwierdza jedynie to, że programista pamiętał o wpisaniu określonej ciągu znaków.

// ❌ Useless: Brittle, tests nothing about accessibility
function Form() {
  return (
    <div data-testid="form-container">
      <input data-testid="email-input" />
      <button data-testid="submit-btn">Submit</button>
    </div>
  );
}
test('submits form', async () => {
  render(<Form />);
  await userEvent.type(screen.getByTestId('email-input'), 'test@example.com');
  await userEvent.click(screen.getByTestId('submit-btn'));
});

Taki rodzaj testu pomyślnie przechodzi nawet wtedy, gdy dane wejściowe nie zawierają etykiety, „przycisk” w rzeczywistości jest tylko stylizowanym div, a użytkownik czytający ekran będzie zupełnie zagubiony. W efekcie potwierdza to błędne zapisy jako poprawne.

// ✅ Useful: Semantic queries enforce accessibility
function Form() {
  return (
    <form aria-label="Login form">
      <label htmlFor="email">Email address</label>
      <input id="email" type="email" />
      <button type="submit">Submit</button>
    </form>
  );
}
test('submits login form', async () => {
  render(<Form />);
  const user = userEvent.setup();

  // These queries fail if the elements are not accessible
  await user.type(screen.getByLabelText(/email address/i), 'test@example.com');
  await user.click(screen.getByRole('button', { name: /submit/i }));

  expect(screen.getByRole('form', { name: /login form/i })).toBeInTheDocument();
});

Zalecany przez Testing Library porządek wykonywania zapytań nie jest arbitralny: getByRole, getByLabelText oraz getByPlaceholderText skłaniają do używania zapisów, które są rzeczywiście dostępne. Jeśli jedynym sposobem na uzyskanie dostępu do elementu jest użycie data-testid, to zazwyczaj oznacza, że sam komponent nie jest dostępny. W obliczu rosnących sporów prawnych dotyczących dostępności napędzanych sztuczną inteligencją do 2026 roku, staje się to czymś więcej niż tylko skrótem w testowaniu – przekształca się w prawdziwe ryzyko prawnicze.

6. Pomijanie oczekiwania na prace asynchroniczne

Prawie wszystko w React działa z opóźnieniem. Zmiany stanu są grupowane, efekty uruchamiają się po narysowaniu strony przez przeglądarkę, a pobieranie danych kończy się dopiero w kolejnym cyklu. Jeśli test nie czeka odpowiednio długo, aż te operacje się zakończą, jego sprawdzenia wykonywane są zbyt wcześnie i test przechodzi, mimo że w rzeczywistości nie sprawdza nic istotnego.

// ❌ Useless: Missing await on async operations
test('shows search results', () => {
  render(<Search />);

  userEvent.type(screen.getByRole('searchbox'), 'react');
  userEvent.keyboard('{Enter}');

  // This runs before the fetch resolves
  expect(screen.getByText('React Testing')).toBeInTheDocument();
});

W zależności od mechanizmów cacheowania lub czasu wykonywania, taki test może przechodzić przez przypadek lub zawieść w nieprzewidywalny sposób. To jest chyba gorsze niż brak jakiegokolwiek testu, ponieważ tworzy fałszywe poczucie bezpieczeństwa.

// ✅ Useful: Explicitly await async states
test('shows search results after typing', async () => {
  render(<Search />);
  const user = userEvent.setup();

  await user.type(screen.getByRole('searchbox'), 'react');
  await user.keyboard('{Enter}');

  // findBy queries wait for the element to appear
  expect(await screen.findByText('React Testing')).toBeInTheDocument();

  // Ensure loading state was shown and removed
  expect(screen.queryByRole('status', { name: /loading/i })).not.toBeInTheDocument();
});

Wchodząc w rok 2026, dzięki haku use z React 19 oraz coraz szerszemu wykorzystaniu granic Suspense, wzorce asynchroniczne pojawiają się niemal wszędzie. Każda interakcja, która powoduje aktualizację stanu, przejście lub wywołanie do sieci, musi zostać odpowiednio oczekiwana. Używaj zapytań findBy, gdy element pojawia się asynchronicznie, stosuj waitFor w przypadku warunków wykraczających poza proste sprawdzanie obecności, i upewnij się, że każde wywołanie userEvent jest oczekiwane.

7. Testowanie komponentów w izolacji od ich rzeczywistego środowiska

Często zdarza się renderować komponent całkowicie samodzielnie — bez routera, bez dostawcy tematu, bez klienta zapytań, bez kontekstu autoryzacji — obserwować, jak test przechodzi, a potem zobaczyć, jak ten sam komponent zawodzi w środowisku produkcyjnym z powodu ukrytego uzależnienia od kontekstu, którego test w ogóle nie dostarczył.

// ❌ Useless: Missing app context
test('renders dashboard', () => {
  render(<Dashboard />);
  expect(screen.getByText('Welcome')).toBeInTheDocument();
});

Ten test niewiele mówi, ponieważ Dashboard prawdopodobnie polega na hookach takich jak useQuery, useAuth lub useNavigate. Bez odpowiednich dostawców nie testujesz faktycznie komponentu — testujesz jego wyimaginowaną wersję, która nie ma odpowiednika w twojej rzeczywistej aplikacji.

// ✅ Useful: Custom render with all providers
import { render as rtlRender } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
function render(ui, { route = '/', ...options } = {}) {
  window.history.pushState({}, 'Test page', route);

  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });

  const Wrapper = ({ children }) => (
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <AuthProvider>
          {children}
        </AuthProvider>
      </BrowserRouter>
    </QueryClientProvider>
  );

  return rtlRender(ui, { wrapper: Wrapper, ...options });
}
// Now your tests run in an environment that matches production
test('renders dashboard for authenticated user', async () => {
  render(<Dashboard />, { route: '/dashboard' });

  expect(await screen.findByText(/welcome back/i)).toBeInTheDocument();
});

Biorąc pod uwagę duży stopień zależności od kontekstu w nowoczesnych architekturach oraz powszechność warstw cache’owania, takich jak React Query, do 2026 roku tworzenie wspólnego, dostosowanego pomocnika renderowania przestaje być opcjonalne — staje się filarem całego twojego zestawu narzędzi. Każdy test powinien korzystać z tej samej grupy dostawców, jaką faktycznie używa aplikacja produkcyjna.

8. Testowanie tylko ścieżki udanej

// ❌ Useless: Only the sunny day scenario
test('displays products', async () => {
  server.use(
    http.get('/api/products', () => HttpResponse.json([{ id: 1, name: 'Widget' }]))
  );

  render(<ProductList />);
  expect(await screen.findByText('Widget')).toBeInTheDocument();
});

Tego typu testy nic nie mówią o odpowiedzi 500, pustym zestawie wyników, prośbie z czasem wygaśnięciem lub sytuacji, gdy użytkownik wysyła błędne dane. Duży odsetek rzeczywistych błędów w produkcji — często szacowany na około osiemdziesiąt procent — kryje się właśnie w tych przypadkach granicznych.

// ✅ Useful: Testing states that actually happen to users
test('displays empty state when no products exist', async () => {
  server.use(http.get('/api/products', () => HttpResponse.json([])));

  render(<ProductList />);
  expect(await screen.findByText(/no products available/i)).toBeInTheDocument();
});
test('displays error state when API fails', async () => {
  server.use(
    http.get('/api/products', () => new HttpResponse(null, { status: 500 }))
  );

  render(<ProductList />);
  expect(await screen.findByRole('alert')).toHaveTextContent(/failed to load/i);
});
test('prevents submission with invalid email', async () => {
  render(<NewsletterForm />);
  const user = userEvent.setup();

  await user.type(screen.getByLabelText(/email/i), 'not-an-email');
  await user.click(screen.getByRole('button', { name: /subscribe/i }));

  expect(screen.getByRole('alert')).toHaveTextContent(/valid email required/i);
  expect(screen.getByRole('button')).toBeDisabled(); // Submit blocked
});

Wraz z tym, że działania formularzy w React 19 oraz optymistyczne aktualizacje interfejsu stają się standardem, sprawdzanie zachowania komponentów w stanach błędu i scenariuszach cofania się jest ważniejsze niż kiedykolwiek. Użytkownicy nieuchronnie napotkają przypadki awarii, a zestaw testów powinien pokazać, że takie sytuacje są faktycznie radzone sobie z nimi.

9. Przenikanie stanu między przypadkami testowymi

Wspólny stan globalny jest częstym źródłem problemów w zestawach testowych. Magazyn Zustand może przenosić wartości z jednego testu do drugiego. Pamięć cache TanStack Query może przechowywać przestarzałe dane. Zmodyfikowany window.location ustawiony w jednym pliku może potajemnie wpłynąć na kolejny test. W rezultacie zestaw testów przepada pojedynczo, ale zawodzi przy jednoczesnym wykonywaniu — albo, co jest jeszcze bardziej mylące, przepada podczas pełnego testowania, ale udaje się przy izolowanym wykonywaniu.

// ❌ Useless: Polluted global state
import { useAuthStore } from './store';
test('logs in user', () => {
  useAuthStore.getState().setUser({ id: 1, name: 'Alice' });
  render(<Profile />);
  expect(screen.getByText('Alice')).toBeInTheDocument();
});
// This test might fail depending on run order
test('shows guest view', () => {
  render(<Profile />);
  // If the previous test ran first, Alice is still logged in
  expect(screen.getByText(/sign in/i)).toBeInTheDocument();
});

Skutkiem jest niestabilny, powolny i niewiarygodny zestaw testów. Gdy tak się stanie, inżynierowie przestają ufać awariom w procesie CI, a testy stają się jedynie szumem tłem, którego nikt nie bada.

// ✅ Useful: Isolated state for every test
import { act } from '@testing-library/react';
beforeEach(() => {
  // Reset Zustand store
  act(() => {
    useAuthStore.setState({ user: null, isAuthenticated: false });
  });

  // Clear React Query cache
  queryClient.clear();

  // Reset MSW handlers
  server.resetHandlers();

  // Reset mocks
  vi.clearAllMocks();
});
test('logs in user', async () => {
  // Test with explicit setup
  act(() => {
    useAuthStore.getState().setUser({ id: 1, name: 'Alice' });
  });

  render(<Profile />);
  expect(screen.getByText('Alice')).toBeInTheDocument();
});
test('shows guest view', () => {
  // Guaranteed clean state
  render(<Profile />);
  expect(screen.getByText(/sign in/i)).toBeInTheDocument();
});

Wchodząc w rok 2026, gdy biblioteki stanów atomowych i wspólne pamięci cache są wszędzie dostępne, kluczowe jest traktowanie izolacji testów jako kwestii architektonicznej, a nie czegoś dodanego na końcu. Każdy test musi utworzyć swój własny czysty kontekst, sprawdzić jego zachowanie, a następnie wszystko to zlikwidować. Dobry test diagnostyczny: jeśli twój zestaw testów nie może być uruchamiany w losowej kolejności, coś w twojej strategii izolacji jest nieprawidłowe.

10. Stwierdzenia, które nigdy nie mogą zawieść

Pośrednim błędem jest być może napisanie testu, który wydaje się poprawny, ale strukturalnie nie jest w stanie wykryć błędu. Dzieje się tak zazwyczaj, gdy stwierdzenie dotyczy niewłaściwej rzeczy, gdy brakuje instrukcji await, lub gdy użyto zbyt pobłażliwego mechanizmu porównywania.

// ❌ Useless: Assertion that cannot fail
test('modal opens', async () => {
  render(<Modal trigger={<button>Open</button>} />);

  // getBy throws if not found, but this is not an assertion
  screen.getByRole('dialog');

  // This passes even if the modal is empty or hidden
  expect(screen.getByRole('dialog')).toBeTruthy();
});
// ❌ Useless: Missing await on expect
test('shows notification', async () => {
  render(<Notifier />);
  await userEvent.click(screen.getByRole('button'));

  // Without await, this checks before the notification appears
  expect(screen.findByText(/success/i)).toBeInTheDocument();
});
// ❌ Useless: Testing presence of truthy object
test('form is valid', () => {
  const { container } = render(<Form />);
  // container is always truthy
  expect(container).toBeTruthy();
});

Takie testy są chyba gorsze niż brak jakichkolwiek testów — nadal zużywają minuty w środowisku CI i dają zespołowi fałszywe poczucie pewności, przy tym nie są w stanie zidentyfikować rzeczywistej regresji, gdy ta wystąpi.

// ✅ Useful: Specific, awaited, negative assertions
test('modal opens and traps focus', async () => {
  render(<Modal trigger={<button>Open</button>} />);
  const user = userEvent.setup();

  await user.click(screen.getByRole('button', { name: /open/i }));

  // Assert visibility, not just presence
  const modal = screen.getByRole('dialog', { name: /confirm/i });
  expect(modal).toBeVisible();

  // Assert focus management
  expect(screen.getByRole('button', { name: /close/i })).toHaveFocus();

  // Assert escape key behavior
  await user.keyboard('{Escape}');
  expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
test('shows success notification after action', async () => {
  render(<Notifier />);
  const user = userEvent.setup();

  await user.click(screen.getByRole('button'));

  // await the findBy, then assert
  const notification = await screen.findByRole('alert');
  expect(notification).toHaveTextContent(/successfully saved/i);
  expect(notification).toHaveAttribute('aria-live', 'polite');
});

Biorąc pod uwagę dalsze dążenie React Testing Library do weryfikacji widocznej dla użytkownika, poleganie na luźnych matcherach takich jak toBeTruthy lub toBeDefined powinno być traktowane jako sygnał ostrzegawczy już w 2026 roku. Należy preferować twierdzenia dotyczące widocznego tekstu, atrybutów ARIA, stanu wyłączonego oraz tego, co faktycznie jest renderowane. Przydatna wskazówka: jeśli twierdzenie nadal przepada przy pustym div, należy je przepisać.

Lista kontrolna testów na rok 2026

Zanim połączysz nowy test, przejrzyj tę listę:

  • [ ] Czy weryfikuje on zachowanie widoczne dla użytkownika, a nie wewnętrzną implementację?
  • [ ] Czy steruje interakcjami za pomocą userEvent, a nie fireEvent?
  • [ ] Czy warstwa sieciowa jest symulowana za pomocą MSW, a nie prostego stuba fetch?
  • [ ] Czy używa celowych stwierdzeń, zamiast polegać na szybkich zdjęciach stanu?
  • [ ] Czy elementy są lokalizowane według roli i etykiety, a nie data-testid?
  • [ ] Czy wszystkie kroki asynchroniczne są prawidłowo oczekiwane?
  • [ ] Czy renderuje się w tej samej hierarchii dostawców, której używa rzeczywista aplikacja?
  • [ ] Czy testuje stany błędu, pustego i ładowania, a nie tylko scenariusz prawidłowego działania?
  • [ ] Czy stan globalny jest resetowany, aby test mógł być wykonywany w dowolnej kolejności?
  • [ ] Czy stwierdzenie rzeczywiście by się nie powiodło, gdyby podstawowe zachowanie uległo awarii?
  • Po prostu otrzymanie zielonej gwiazdki od zestawu testów nie jest prawdziwym celem — kluczowe jest zdobycie pewności co do wiarygodności tej gwiazdki. Uniknij tych dziesięciu pułapek, a twoje testy przestaną być jedynie formalnością i staną się rzeczywistym zabezpieczeniem, jakim powinny być.