Przejdź do treści

Cypress Component Testing w React i Next.js — kiedy naprawdę ma sens

Cypress Component Testing to nie RTL w przeglądarce — inne mocne strony, inne ograniczenia. Kiedy warto po niego sięgać w projekcie Next.js?

Maciej Sala

Founder StriveLab

5 min czytaniaOpublikowano 6 października 2025 (Aktualizacja 25 maja 2026)

Czym Cypress Component Testing różni się od testów E2E?

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.

Code
E2E:   Cypress → przeglądarka → Twoja aplikacja (next dev) → komponent
CT:    Cypress → przeglądarka → komponent (bezpośrednio)

W ten sposób, testy CT są szybsze, bardziej izolowane i łatwiejsze do debugowania.

Setup Cypress Component Testing w Next.js i TypeScript

Instalacja Cypress Component Testing

Code
npm install -D cypress
npx 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.

Konfiguracja Cypress CT w projekcie Next.js

Code
// cypress.config.ts
import { defineConfig } from 'cypress'
 
export default defineConfig({
  component: {
    devServer: {
      framework: 'next',
      bundler: 'webpack',
    },
    specPattern: 'cypress/component/**/*.cy.{ts,tsx}',
    supportFile: 'cypress/support/component.ts',
  },
})

Support file dla Cypress Component Testing

Code
// cypress/support/component.ts
import './commands'
import { mount } from 'cypress/react' // Cypress 13+; w Cypress 10–12 użyj 'cypress/react18'
 
declare global {
  namespace Cypress {
    interface Chainable {
      mount: typeof mount
    }
  }
}
 
Cypress.Commands.add('mount', mount)

Pierwszy test komponentu w Cypress CT

Zacznijmy od prostego komponentu Alert:

Code
// components/Alert.tsx
interface AlertProps {
  type: "success" | "error" | "warning";
  message: string;
  onClose?: () => void;
}
 
export function Alert({ type, message, onClose }: AlertProps) {
  return (
    <div role="alert" className={`alert alert-${type}`}>
      <span>{message}</span>
      {onClose && (
        <button onClick={onClose} aria-label="Zamknij">
          ×
        </button>
      )}
    </div>
  );
}
Code
// cypress/component/Alert.cy.tsx
import { Alert } from "@/components/Alert";
 
describe("Alert", () => {
  it("renders success message", () => {
    cy.mount(<Alert type="success" message="Zapisano pomyślnie!" />);
 
    cy.get("[role='alert']")
      .should("be.visible")
      .and("have.class", "alert-success");
    cy.contains("Zapisano pomyślnie!");
  });
 
  it("renders error variant", () => {
    cy.mount(<Alert type="error" message="Coś poszło nie tak" />);
 
    cy.get("[role='alert']").should("have.class", "alert-error");
  });
 
  it("calls onClose when dismiss button is clicked", () => {
    const onClose = cy.stub().as("onCloseHandler");
 
    cy.mount(
      <Alert type="warning" message="Uwaga!" onClose={onClose} />
    );
 
    cy.get("[aria-label='Zamknij']").click();
    cy.get("@onCloseHandler").should("have.been.calledOnce");
  });
 
  it("does not render close button without onClose prop", () => {
    cy.mount(<Alert type="success" message="Info" />);
 
    cy.get("[aria-label='Zamknij']").should("not.exist");
  });
});

Uruchom testy:

Code
npx cypress open --component

Testowanie komponentów React z hookami i stanem

Code
// components/SearchInput.tsx
import { useState, useEffect } from "react";
import { useDebounce } from "@/hooks/useDebounce";
 
interface SearchInputProps {
  onSearch: (query: string) => void;
  placeholder?: string;
  debounceMs?: number;
}
 
export function SearchInput({
  onSearch,
  placeholder = "Szukaj...",
  debounceMs = 300,
}: SearchInputProps) {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, debounceMs);
 
  useEffect(() => {
    if (debouncedQuery) {
      onSearch(debouncedQuery);
    }
  }, [debouncedQuery, onSearch]);
 
  return (
    <input
      type="search"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder={placeholder}
      aria-label="Pole wyszukiwania"
    />
  );
}
Code
// cypress/component/SearchInput.cy.tsx
import { 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 komponentów 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 – rozszerzony
import { 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 Component Testing vs Jest i React Testing Library

AspektCypress CTJest + RTL
ŚrodowiskoPrawdziwa przeglądarkajsdom (symulacja)
CSSDziała (widoczne wizualnie)Ignorowane
Visual debuggingTime-travel, snapshotyConsole.log
SzybkośćWolniejsze (przeglądarka)Szybsze (Node.js)
Event handlingNatywne eventy przeglądarkiSymulowane
SetupWymaga konfiguracji webpackProsta konfiguracja
CIWymaga headless browserNie wymaga

Kiedy Cypress Component Testing 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 i React Testing Library wygrywają?

  • Kiedy szybkość jest priorytetem (),
  • 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 Cypress Component Testing

Cypress CT wspiera cy.intercept() tak samo jak E2E:

Code
// cypress/component/UserProfile.cy.tsx
import { 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 Cypress Component Testing 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,
  • Cypress/Playwright E2E: krytyczne flow użytkownika, routing, autentykacja, checkout.

Dobre praktyki Cypress Component Testing w React i Next.js

Organizacja plików – trzymaj testy CT blisko komponentów lub w dedykowanym cypress/component/:

Code
├── components/
│   ├── Alert.tsx
│   └── SearchInput.tsx
├── cypress/
│   ├── component/
│   │   ├── Alert.cy.tsx
│   │   └── SearchInput.cy.tsx
│   └── e2e/
│       └── checkout.cy.ts

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. Kompletny setup Cypress E2E w App Router — od konfiguracji CI po fixtures i custom commands — opisuję w osobnym artykule o testach E2E w Next.js.

Testy automatyczne komponentów i E2E w Cypress.
QA & Automation

Często zadawane pytania

Czy Cypress Component Testing zastępuje React Testing Library?

Nie. Cypress Component Testing uzupełnia RTL tam, gdzie ważna jest prawdziwa przeglądarka, CSS, focus, drag and drop albo interakcje trudne do wiarygodnego sprawdzenia w jsdom.

Kiedy lepiej wybrać test E2E zamiast component testu?

Gdy chcesz sprawdzić routing, autoryzację, integrację z backendem, Server Components albo pełny przepływ użytkownika między ekranami. Component testing jest najlepszy dla izolowanych komponentów UI.

Czym jest Cypress Component Testing?

Cypress Component Testing (CT) to tryb testowania, w którym montujesz pojedynczy komponent React bezpośrednio w przeglądarce Cypress i to bez uruchamiania całej aplikacji i serwera dev. Otrzymujesz w ten sposób prawdziwe środowisko przeglądarki, działający CSS, natywne eventy DOM, time-travel debugging. To dodatkowa warstwa między unit testami (jsdom), a pełnymi testami E2E.

Jaka różnica między Cypress CT, a Jest + React Testing Library?

Cypress CT działa w prawdziwej przeglądarce, co oznacza, że CSS jest aplikowany, eventy są natywne, a animacje działają - daje pewność, że komponent zachowuje się jak w produkcji, co jest wartościowe dla komponentów CSS-intensywnych i złożonych interakcji DOM. Jest + RTL używa jsdom, czyli symuluje przeglądarkę bez prawdziwego renderowania CSS, natomiast sam RTL jest szybszy (Node.js, nie przeglądarka) i prostszy w konfiguracji.

Kiedy wybrać Cypress CT zamiast RTL?

Cypress CT wygrywa gdy: (1) komponent intensywnie używa CSS (animacje, media queries, focus states); (2) testujesz interakcje drag & drop, scroll, resize; (3) zależy Ci na wizualnym debugowaniu i inspekcji w przeglądarce; (4) już używasz Cypress E2E i chcesz spójnego workflow. RTL wygrywa gdy: (1) szybkość w CI jest priorytetem; (2) testujesz logikę i hooki; (3) masz prosty stack bez CSS dependencies.

Czy można testować Next.js Server Components w Cypress CT?

Nie bezpośrednio. Server Components wykonują się po stronie serwera i zwracają serialized payload — nie są komponentami React, które można zamontować w przeglądarce. Do testowania Server Components używaj testów integracyjnych lub E2E. Cypress CT najlepiej sprawdza się dla Client Components z 'use client', które mają lokalny stan, handlery zdarzeń i bezpośrednie interakcje z DOM.

Jak skonfigurować Cypress Component Testing w Next.js?

Instalacja: npm install -D cypress. Przy pierwszym npx cypress open wybierz "Component Testing" i framework Next.js — Cypress wygeneruje bazową konfigurację. W cypress.config.ts ustaw component.devServer.framework: 'next' i bundler: 'webpack'. Dodaj support file z Cypress.Commands.add('mount', mount). Dla komponentów zależnych od kontekstu (React Query, Theme) stwórz wrapper w support file.

Czy Cypress CT zastępuje testy E2E?

Nie. CT i E2E testują różne rzeczy, ponieważ CT oznacza izolowany komponent, szybkie wykonanie, bez routingu ani serwera. E2E wiąże się z pełną aplikacją uruchomioną jak w produkcji, realne przepływy użytkownika przez wiele stron. Co wymaga E2E? Routing, middleware, auth, redirecty, generateMetadata czy Server Actions. Optymalny podział to RTL dla logiki, CT dla interaktywnych komponentów z realnym DOM, a E2E dla krytycznych flow.

Jak mockować API w Cypress Component Testing?

Cypress CT wspiera cy.intercept() tak samo jak E2E, czyli przechwytuje requesty HTTP i zwraca mock response. W teście: cy.intercept('GET', '/api/user', { body: mockUser }), potem montuj komponent i weryfikuj render. Możesz też używać cy.stub() do mockowania propsów będących callbackami. Dla komponentów używających React Query warto skonfigurować QueryClient z retry: false w wrapperze testowym.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwijam własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
Cypress vs Playwright – który wybrać do projektu Next.js?

Dogłębne porównanie Cypress i Playwright w kontekście projektów Next.js. doświadczenie deweloperskie, szybkość, wsparcie dla SSR i React – który framework testowy wybrać?

Maciej Sala

Maciej Sala

Founder StriveLab

E2E testy w Next.js App Router – kompletny setup Cypress + CI/CD

Od instalacji, przez fixtures i custom commands, po integrację z GitHub Actions. Kompletny przewodnik po konfiguracji testów E2E Cypress w projekcie Next.js z App Router.

Maciej Sala

Maciej Sala

Founder StriveLab

Automatyczne testy regresji SEO w GitHub Actions — noindex, link kanoniczny i redirecty pod kontrolą CI/CD

Jedna linijka w niewłaściwym miejscu — , która miała zostać tylko na środowisku testowym — i strona znika z Google na tygodnie. To jeden z najdroższych błędów w SEO technicznym, ponieważ długo pozostaje niewidoczny: build przechodzi, strona działa, użytkownicy niczego nie zauważają, a ruch organiczny po cichu się osuwa. Dobra wiadomość jest taka, że tę klasę błędów da się złapać automatycznie, zanim kod w ogóle trafi na produkcję. W tym artykule pokazuję, jak zbudować testy regresji SEO w Playwright i wpiąć je w GitHub Actions , żeby pull request z zepsutym linkiem kanonicznym czy przypadkowym noindexem po prostu nie przeszedł.

Maciej Sala

Maciej Sala

Founder StriveLab