Cypress Component Testing w React i Next.js — kiedy naprawdę ma sens
Cypress Component Testing w React i Next.js bez marketingowej mgły. Kiedy daje przewagę nad RTL, jak go skonfigurować i gdzie kończą się jego możliwości.
Różnica jest zasadnicza: w tradycyjnym Cypress E2E musisz mieć uruchomiony serwer dev, cała aplikacja jest włączona i działa, a Cypress nawiguje po stronach jak użytkownik. Z kolei w Component Testing montujesz pojedynczy komponent (lub drzewo komponentów) bezpośrednio w przeglądarce Cypress.
W ten sposób, testy CT są szybsze, bardziej izolowane i łatwiejsze do debugowania.
Setup: Next.js + TypeScript + Cypress CT
Instalacja
Code
npm install -D cypressnpx cypress open
Przy pierwszym uruchomieniu Cypress daje nam wybór pomiędzy E2E Testing, a Component Testing - wybierz Component Testing i pozwól mu wygenerować bazową konfigurację. W przypadku Next.js Component Testing, opiera się zwykle na integracji z Webpackiem, więc warto potraktować to jako osobną warstwę testów dla komponentów, a nie próbę uruchamiania całej aplikacji App Router 1:1 wewnątrz Cypressa.
// cypress/component/SearchInput.cy.tsximport { SearchInput } from "@/components/SearchInput";describe("SearchInput", () => { it("calls onSearch after debounce", () => { cy.clock(); // przejmuje kontrolę nad timerami przeglądarki const onSearch = cy.stub().as("searchHandler"); cy.mount(<SearchInput onSearch={onSearch} debounceMs={200} />); cy.get("[aria-label='Pole wyszukiwania']").type("React"); // Przed debounce – onSearch nie powinien być wywołany cy.get("@searchHandler").should("not.have.been.called"); // Przesuń czas o 200 ms – debounce odpala się dokładnie teraz cy.tick(200); cy.get("@searchHandler").should("have.been.calledWith", "React"); }); it("renders custom placeholder", () => { cy.mount( <SearchInput onSearch={cy.stub()} placeholder="Znajdź produkt..." /> ); cy.get("input").should("have.attr", "placeholder", "Znajdź produkt..."); });});
Testowanie z kontekstem i providerami
Wiele komponentów React zależy od kontekstu (Theme, Auth, Router), natomiast w CT musisz dostarczyć te providery ręcznie.
Code
// cypress/support/component.ts – rozszerzonyimport { mount } from "cypress/react";import { ThemeProvider } from "@/context/ThemeContext";import { QueryClient, QueryClientProvider } from "@tanstack/react-query";function createWrapper() { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, }, }); return function Wrapper({ children }: { children: React.ReactNode }) { return ( <QueryClientProvider client={queryClient}> <ThemeProvider>{children}</ThemeProvider> </QueryClientProvider> ); };}Cypress.Commands.add("mount", (component, options = {}) => { const Wrapper = createWrapper(); return mount(<Wrapper>{component}</Wrapper>, options);});
Teraz każdy cy.mount() automatycznie opakowuje komponent w potrzebne providery.
Cypress CT vs Jest + React Testing Library
Aspekt
Cypress CT
Jest + RTL
Środowisko
Prawdziwa przeglądarka
jsdom (symulacja)
CSS
Działa (widoczne wizualnie)
Ignorowane
Visual debugging
Time-travel, snapshoty
Console.log
Szybkość
Wolniejsze (przeglądarka)
Szybsze (Node.js)
Event handling
Natywne eventy przeglądarki
Symulowane
Setup
Wymaga konfiguracji webpack
Prosta konfiguracja
CI
Wymaga headless browser
Nie wymaga
Kiedy Cypress CT wygrywa?
W telegraficznym skrócie:
Komponent intensywnie korzysta z CSS (animacje, responsive layout),
Zależy Ci na wizualnym debugowaniu,
Testujesz interakcje drag & drop, scroll, resize,
Chcesz spójności z istniejącymi testami Cypress E2E.
Kiedy Jest + RTL wygrywa?
Kiedy szybkość jest priorytetem (CI/CD (Continuous Integration / Continuous Deployment) to praktyka automatycznego integrowania, testowania i wdrażania kodu. CI uruchamia testy i build przy każdej zmianie, CD automatyzuje dostarczenie zmiany na środowisko docelowe — razem skracają drogę od commita do produkcji i wyłapują błędy wcześnie.),
Testujesz logikę, a nie wygląd i masz prosty setup bez ciężkich zależności CSS,
Twój zespół już zna i używa z powodzeniem RTL.
Interceptowanie requestów w CT
Cypress CT wspiera cy.intercept() tak samo jak E2E:
Code
// cypress/component/UserProfile.cy.tsximport { UserProfile } from "@/components/UserProfile";describe("UserProfile", () => { it("displays user data from API", () => { cy.intercept("GET", "/api/user/1", { statusCode: 200, body: { name: "Jan Kowalski", email: "jan@example.com" }, }).as("getUser"); cy.mount(<UserProfile userId="1" />); cy.wait("@getUser"); cy.contains("Jan Kowalski"); cy.contains("jan@example.com"); }); it("shows error state on API failure", () => { cy.intercept("GET", "/api/user/1", { statusCode: 500, body: { error: "Internal Server Error" }, }); cy.mount(<UserProfile userId="1" />); cy.contains(/nie udało się załadować/i); });});
Ograniczenia w projektach Next.js
To jest miejsce, w którym wiele osób próbuje wycisnąć z CT za dużo.
Client Components nadają się bardzo dobrze do CT, poniważ posiadają lokalny stan, eventy i zależności od DOM.
Server Components lepiej testować przez integrację lub E2E, ponieważ ich wartość wynika z renderowania po stronie serwera, cachingu i kompozycji z resztą aplikacji.
Routing App Routera nie jest celem CT. Jeśli chcesz testować przejścia między stronami, middleware, auth guardy czy redirect(), pisz testy E2E.
generateMetadata, generateStaticParams, route handlers i cache Next.js to nie są "zachowania komponentu" i nie warto na siłę wrzucać ich do CT.
Dobry podział odpowiedzialności wygląda zwykle tak:
Vitest/RTL: czysta logika, hooki, komponenty bez ciężkiego CSS,
Cypress CT: komponenty interaktywne, layout zależny od przeglądarki, drag and drop, focus states,
Data-testid vs role – preferuj role i aria-label (jak RTL), ale data-testid to pragmatyczny fallback.
Nie testuj w CT tego, co lepiej sprawdzi E2E – nawigacja między stronami, pełne flow użytkownika i integracja wielu komponentów to domena testów E2E.
Często zadawane pytania
O autorze
Maciej Sala
Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.
Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.