Jak skonfigurować testy jednostkowe w monorepo React i Node z Jest i Vitest

Marek Radoszewski Marek Radoszewski
Narzędzia i Praktyki
15.06.2026 11 min
Jak skonfigurować testy jednostkowe w monorepo React i Node z Jest i Vitest

Wprowadzenie do szybkich testów w monorepo React i Node

Zastanawiasz się, jak skonfigurować testy jednostkowe w monorepo React i Node z Jest i Vitest, aby skrócić czas buildów o połowę? W złożonych projektach, gdzie w jednym repozytorium żyje wiele aplikacji i bibliotek, długie buildy potrafią zabić produktywność. Każde dodatkowe uruchomienie testów wydłuża feedback i zniechęca do częstego sprawdzania jakości.

Monorepo daje ogromne korzyści – współdzielenie kodu, spójne narzędzia, łatwiejsze zarządzanie zależnościami. Jednocześnie rośnie cena każdego pełnego builda i kompletu testów. Im więcej pakietów, tym większe ryzyko, że po małej zmianie będziesz patrzeć w terminal przez 20 minut, zamiast pisać kod.

W tym przewodniku zobaczysz, jak połączyć Jest i Vitest w jednym monorepo, aby wykorzystać moc obu narzędzi tam, gdzie sprawdzają się najlepiej. Skupimy się na praktycznych konfiguracjach oraz strategiach uruchamiania tylko tych testów, które faktycznie mają sens, zamiast bezrefleksyjnego „odpal wszystko”.

Najważniejsza idea: nie chodzi o zastąpienie jednego frameworka drugim, ale o hybrydowe podejście. Wykorzystasz Vitest tam, gdzie liczy się szybkość frontendu, i zostawisz Jest tam, gdzie daje stabilność i dojrzały ekosystem, szczególnie w backendzie i starszych projektach.

Schemat monorepo z aplikacjami React i Node pokazujący podział testów między Jest i Vitest w celu skrócenia czasu buildów o połowę

Dlaczego konfiguracja testów w monorepo jest tak krytyczna?

W monorepo każdy dodatkowy pakiet to potencjalne wydłużenie całego procesu CI/CD. Czas trwania testów i buildów wpływa bezpośrednio na tempo pracy zespołu. Nawet świetna architektura traci sens, jeśli na potwierdzenie poprawności jednej zmiany trzeba czekać kilkanaście minut.

Długie czasy buildów mają kilka kluczowych konsekwencji. Po pierwsze, obniżają produktywność deweloperów – przerywają „flow” i zmuszają do częstego przełączania kontekstu. Po drugie, wydłużają cykle feedbacku, co opóźnia wykrycie błędów. To z kolei zwiększa ryzyko problemów integracyjnych i opóźnień w wdrożeniach.

Dochodzi do tego aspekt finansowy. Wolne buildy obciążają infrastrukturę CI/CD, zwiększając jej koszty utrzymania. Gdy testy są zbyt drogie czasowo, pojawia się pokusa uruchamiania ich rzadziej, co prowadzi do jeszcze większego ryzyka regresji. Rozwiązaniem jest system, który uruchamia tylko potrzebne testy i korzysta z najszybszych dostępnych narzędzi.

W tym kontekście sens ma rozdzielenie ról: Vitest dla nowoczesnych aplikacji frontendowych opartych o Vite oraz Jest dla backendu Node.js i starszych projektów React. Dzięki temu skrócisz czas reakcji testów, a jednocześnie zachowasz stabilność tam, gdzie migracja byłaby kosztowna.

Dlaczego warto łączyć Jest i Vitest w jednym monorepo?

Jest to dominujący framework testowy w ekosystemie JavaScript. Jest rozpoznawalny, stabilny, bogaty we wtyczki i integracje. Sprawdza się świetnie w aplikacjach Node.js oraz w wielu istniejących projektach React, szczególnie tych opartych na starszych konfiguracjach czy Create React App. Jego ogromną zaletą jest dojrzałość i przewidywalność.

Jednocześnie Jest bywa ciężki dla nowoczesnych, szybkich frontendów. Start procesów, integracja z transpilatorami (Babel, SWC, ts-jest), rozbudowane środowisko – wszystko to wpływa na czas uruchamiania testów. W świecie Vite i bardzo szybkich bundlerów może to być wyraźnie odczuwalne.

Tu wchodzi Vitest, zbudowany na bazie Vite. Oferuje błyskawiczne uruchamianie testów, świetne wsparcie dla React i HMR, a konfiguracja dla projektów Vite jest wyjątkowo prosta. Dla nowoczesnych aplikacji frontendowych to często najbardziej naturalny wybór. Integracja z ekosystemem Vite oznacza mniej konfiguracji i więcej korzyści „z pudełka”.

Hybrydowe podejście wygląda w praktyce tak:

  • Vitest dla nowoczesnych aplikacji React z Vite – maksymalna szybkość i prosta konfiguracja.
  • Jest dla projektów Node.js i tych frontów, które już mają stabilną konfigurację na Jest, gdzie migracja do Vitest nie jest priorytetem.

Zyskujesz „dwa samochody” do różnych zadań: Vitest jako superszybki bolid na autostradę frontendu i Jest jako zaufany, dopracowany „wołek roboczy” w backendzie i legacy kodzie. W monorepo taka specjalizacja jest wyjątkowo opłacalna.

Struktura monorepo i wybór menadżera pakietów

Aby sensownie skonfigurować testy jednostkowe w monorepo React i Node, potrzebujesz solidnej struktury katalogów oraz spójnego zarządzania zależnościami. Najczęściej stosuje się Yarn Workspaces, pnpm Workspaces, czasem z dodatkami typu Lerna lub Nx, które ułatwiają pracę na wielu projektach.

Przykładowa struktura monorepo może wyglądać tak:

my-monorepo/
├── package.json
├── packages/
│   ├── backend-api/
│   │   ├── package.json
│   │   └── src/
│   ├── frontend-app/
│   │   ├── package.json
│   │   └── src/
│   ├── shared-ui/
│   │   ├── package.json
│   │   └── src/
│   └── another-service/
│       ├── package.json
│       └── src/

W przypadku Yarn Workspaces deklarujesz workspaces w głównym package.json:

{
  "name": "my-monorepo",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "test": "yarn workspaces run test"
  }
}

Dla pnpm Workspaces używasz pliku pnpm-workspace.yaml:

packages:
  - 'packages/*'

A w głównym package.json dodajesz między innymi:

{
  "name": "my-monorepo",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "test": "pnpm -r test"
  }
}

Taka struktura pozwala na lokalne definiowanie testów w każdym pakiecie, a jednocześnie daje możliwość globalnego uruchamiania testów wszystkich projektów z poziomu katalogu głównego. To fundament pod dalszą konfigurację Jest i Vitest dla poszczególnych aplikacji.

Konfiguracja Jest dla Node.js i starszych projektów React

Instalacja i podstawowa konfiguracja Jest w backendzie

Dla pakietów backendowych, takich jak backend-api, Jest jest naturalnym wyborem. Do projektów TypeScript instalujesz zestaw narzędzi wspierających kompilację i typy:

cd packages/backend-api
npm install --save-dev jest @types/jest ts-jest @types/node
# lub
pnpm add -D jest @types/jest ts-jest @types/node

Następnie definiujesz konfigurację w pliku jest.config.js w katalogu pakietu:

/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['<rootDir>/src/**/*.test.ts', '<rootDir>/src/**/*.spec.ts'],
  collectCoverageFrom: [
    'src/**/*.{ts,js}',
    '!src/**/*.d.ts',
  ],
  coverageDirectory: 'coverage',
  globals: {
    'ts-jest': {
      tsconfig: '<rootDir>/tsconfig.json',
    },
  },
};

Taka konfiguracja sprawia, że testy jednostkowe backendu są uruchamiane w środowisku Node, z pełnym wsparciem TypeScript i raportowaniem pokrycia kodu. Wzorce testMatch jasno określają, gdzie szukać plików testowych.

Użycie Jest w projektach React bazujących na starszych konfiguracjach

Jeżeli masz frontendową aplikację React, która nadal korzysta z dotychczasowej konfiguracji (np. CRA), możesz pozostać przy Jest. Wtedy przydaje się konfiguracja z jsdom oraz testami komponentów:

/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
    '^.+\\.(js|jsx)$': 'babel-jest',
  },
  moduleNameMapper: {
    '\\.(css|less|sass|scss)$': 'identity-obj-proxy',
    '\\.(gif|ttf|eot|svg|png)$': '<rootDir>/__mocks__/fileMock.js',
  },
  setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
  testMatch: [
    '<rootDir>/src/**/*.test.ts',
    '<rootDir>/src/**/*.test.tsx',
    '<rootDir>/src/**/*.spec.ts',
    '<rootDir>/src/**/*.spec.tsx'
  ],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx,js,jsx}',
    '!src/**/*.d.ts',
  ],
  coverageDirectory: 'coverage',
  globals: {
    'ts-jest': {
      tsconfig: '<rootDir>/tsconfig.json',
    },
  },
};

Dodatkowy plik __mocks__/fileMock.js:

module.exports = 'test-file-stub';

W package.json każdego pakietu dodajesz odpowiednie skrypty:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

W ten sposób backend i starsze fronty korzystają z dojrzałego ekosystemu Jest, bez konieczności natychmiastowej migracji do nowych narzędzi.

Konfiguracja Vitest dla nowoczesnych aplikacji React z Vite

Instalacja Vitest i integracja z Vite

Dla nowoczesnych aplikacji React zbudowanych na Vite, najlepszą opcją jest Vitest. Jego konfiguracja jest prosta, ponieważ używa tego samego ekosystemu, co Vite. W pakiecie frontend-app instalujesz potrzebne zależności:

cd packages/frontend-app
npm install --save-dev vitest @vitest/coverage-v8 @testing-library/react @testing-library/jest-dom jsdom
# lub pnpm

Vitest korzysta bezpośrednio z vite.config.ts. Wystarczy rozszerzyć go o sekcję test:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/setupTests.ts'],
    css: true,
    exclude: [
      '**/node_modules/**',
      '**/dist/**',
      '**/e2e/**',
      '**/.{idea,git,cache,output,temp}/**',
      '**/{karma,rollup,webpack,vite,vitest,jest,playwright,cypress}.config.*',
    ],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['src/**/*.d.ts', 'src/main.tsx'],
    },
  },
});

Tworzysz też plik src/setupTests.ts, gdzie możesz dodać globalne rozszerzenia:

import '@testing-library/jest-dom';

W package.json aplikacji React dodasz skrypty:

{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint . --ext ts,tsx --report-unused-files --max-warnings 0",
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:coverage": "vitest run --coverage"
  }
}

Taka konfiguracja pozwala na błyskawiczne testy komponentów React z wykorzystaniem jsdom i Testing Library, a dzięki integracji z Vite zyskujesz HMR oraz optymalizacje bundlowania podczas testowania.

Diagram porównujący Vitest i Jest w monorepo React i Node, pokazujący przepływ testów jednostkowych i skrócony czas buildów

Strategia użycia Jest i Vitest, aby realnie skrócić buildy

Dedykowane skrypty testowe dla różnych pakietów

Pierwszy krok do skrócenia czasu testów to czytelne rozdzielenie ról. W głównym package.json możesz zdefiniować skrypty odwołujące się do konkretnych pakietów:

{
  "scripts": {
    "test": "yarn workspaces run test",
    "test:backend": "yarn workspace backend-api test",
    "test:frontend": "yarn workspace frontend-app test"
  }
}

Lub analogicznie dla pnpm z użyciem filtrów. Dzięki temu łatwo uruchomisz tylko testy backendu albo tylko testy frontendu, bez przepalania czasu na całą resztę. To jednak wciąż nie rozwiązuje problemu uruchamiania testów wyłącznie dla zmienionych projektów.

Wykorzystanie Nx do testów „affected” projektów

Aby naprawdę zmniejszyć czas buildów o połowę, potrzebujesz narzędzia, które zna zależności między projektami. Nx buduje graf zależności i rozumie, które pakiety są dotknięte daną zmianą oraz które inne od nich zależą.

Po zainstalowaniu Nx i inicjalizacji w monorepo, możesz zdefiniować konfigurację projektów. Dla backendu:

{
  "name": "backend-api",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "packages/backend-api/src",
  "projectType": "library",
  "targets": {
    "test": {
      "executor": "@nx/jest:jest",
      "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
      "options": {
        "jestConfig": "packages/backend-api/jest.config.js",
        "passWithNoTests": true
      }
    }
  }
}

Dla frontendu z Vitest:

{
  "name": "frontend-app",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "packages/frontend-app/src",
  "projectType": "application",
  "targets": {
    "test": {
      "executor": "@nx/vite:test",
      "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
      "options": {
        "vitestConfig": "packages/frontend-app/vite.config.ts"
      }
    }
  }
}

Teraz możesz uruchomić:

nx affected:test

Nx obliczy, które pakiety zostały zmodyfikowane (oraz ich zależności w górę grafu), a następnie uruchomi odpowiednie testy – Jest dla Node.js, Vitest dla Vite/React. Dodatkowo Nx posiada wbudowany cache wyników, więc jeśli nic się nie zmieniło w danym projekcie, potrafi zwrócić zapisany rezultat bez ponownego uruchamiania testów. To właśnie tutaj możesz zaobserwować kilkukrotne skrócenie czasu CI.

Własny skrypt do uruchamiania testów tylko dla zmienionych pakietów

Jeżeli nie korzystasz z Nx, możesz stworzyć prostsze rozwiązanie oparte na Git. Idea jest prosta: sprawdzasz listę zmienionych plików, przypisujesz je do pakietów i tylko w nich odpalasz testy.

Przykładowy skrypt scripts/run-affected-tests.js:

const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');

const changedFiles = execSync('git diff --name-only HEAD^ HEAD')
  .toString()
  .split('\n')
  .filter(Boolean);

const packageDirs = fs.readdirSync(path.join(__dirname, '../packages'));

const affectedPackages = new Set();

changedFiles.forEach(file => {
  for (const pkgDir of packageDirs) {
    if (file.startsWith(path.join('packages', pkgDir))) {
      affectedPackages.add(pkgDir);
      break;
    }
  }
});

if (affectedPackages.size === 0) {
  console.log('Brak zmienionych pakietów. Pomijam testy.');
  process.exit(0);
}

console.log(`Zmienione pakiety: ${Array.from(affectedPackages).join(', ')}`);

for (const pkg of affectedPackages) {
  const pkgPath = path.join(__dirname, '../packages', pkg);
  const pkgJsonPath = path.join(pkgPath, 'package.json');
  if (!fs.existsSync(pkgJsonPath)) {
    console.warn(`Brak pliku package.json dla ${pkg}. Pomijam.`);
    continue;
  }

  const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));

  if (pkgJson.devDependencies && pkgJson.devDependencies.vitest) {
    console.log(`Uruchamiam Vitest dla pakietu: ${pkg}`);
    execSync(`pnpm --filter ${pkg} test`, { stdio: 'inherit' });
  } else if (pkgJson.devDependencies && pkgJson.devDependencies.jest) {
    console.log(`Uruchamiam Jest dla pakietu: ${pkg}`);
    execSync(`pnpm --filter ${pkg} test`, { stdio: 'inherit' });
  } else {
    console.warn(`Brak konfiguracji testów dla pakietu: ${pkg}. Pomijam.`);
  }
}

W głównym package.json dodajesz:

{
  "scripts": {
    "test:affected": "node scripts/run-affected-tests.js"
  }
}

To podejście jest prostsze niż Nx, ale nadal pozwala uruchamiać testy tylko tam, gdzie naprawdę zaszły zmiany. Nie obsługuje jednak grafu zależności, co Nx robi automatycznie.

Dodatkowe przyspieszenia: cache, paralelizacja i tryb watch

Aby jeszcze mocniej skrócić czas wykonywania testów jednostkowych w monorepo React i Node, warto skorzystać z mechanizmów dostępnych w samych frameworkach.

W Jest:

  • Domyślnie działa cache testów, zapisując wyniki i przyspieszając kolejne uruchomienia.
  • Opcja --onlyChanged pozwala uruchamiać testy tylko dla plików zmodyfikowanych od ostatniego commita lub uruchomienia.
  • Tryb --watch lub --watchAll pozwala na szybki feedback w trakcie developmentu.
  • Parametr --maxWorkers umożliwia kontrolę stopnia paralelizacji testów na wielu rdzeniach CPU.

W Vitest:

  • Silna integracja z Vite oznacza naturalny cache i HMR – zmieniasz plik i testy przeładowują się błyskawicznie, bez pełnego restartu.
  • Domyślny model wykonania testów jest bardzo szybki i dobrze korzysta z równoległości.
  • Konfiguracja w vite.config.ts pozwala dostosować zachowanie, np. liczbę workerów czy sposób raportowania coverage.

W środowisku CI/CD warto też zadbać o cache na poziomie platformy – cache node_modules, cache testów i cache budowania Vite. W połączeniu z inteligentnym wyborem tego, które testy uruchamiać, stanowi to dużą część „magii” skracającej buildy nawet o połowę.

Wskazówki praktyczne i podsumowanie

Przy wdrażaniu hybrydowego systemu testów w monorepo warto pamiętać o kilku zasadach. Po pierwsze, stawiaj na stopniową migrację. Nie musisz przenosić wszystkich testów z Jest do Vitest. Nowe aplikacje React z Vite buduj od razu na Vitest, starsze zostaw na Jest i migruj je tylko wtedy, gdy faktycznie przyniesie to wartość.

Po drugie, pilnuj konsekwencji w zespole. Jeżeli ustalacie, że każdy nowy frontend Vite używa Vitest, trzymajcie się tego konsekwentnie. Zmniejszy to złożoność i ułatwi utrzymanie. Backend Node.js niech ma domyślnie konfigurację na Jest, która jest dobrze znana i dopracowana.

Po trzecie, integruj strategie typu nx affected:test lub własne test:affected z pipeline’ami CI/CD. To tam zobaczysz największą różnicę w czasie trwania buildów i kosztach infrastruktury. Pamiętaj też o dobrych praktykach testowania – małe, szybkie testy jednostkowe, sensowne pokrycie, unikanie zbędnych zależności.

Na koniec – monitoruj i optymalizuj. Obserwuj czasy poszczególnych etapów CI, identyfikuj najwolniejsze pakiety, analizuj, czy niektóre testy są zbyt ciężkie. Hybrydowe podejście z Jest i Vitest nie jest jednorazowym trikiem, ale fundamentem do ciągłej poprawy. W ten sposób konfiguracja testów jednostkowych w monorepo React i Node staje się nie tylko szybsza, ale też znacznie bardziej przewidywalna i komfortowa dla całego zespołu.

Marek Radoszewski

Autor

Marek Radoszewski

Freelance developer i tech blogger od 7 lat. Pracował przy projektach dla klientów z Polski, UK i USA. Na blogu pisze o praktycznych aspektach programowania, narzędziach i tym, jak skutecznie rozwijać karierę jako niezależny programista.

Wróć do kategorii Narzędzia i Praktyki