Quick Start: New to testing? Jump to the 5-Minute Quick Start to get running immediately!
This document describes the complete testing infrastructure for the Ground Station frontend application.
- Quick Start (5 Minutes)
- Testing Stack
- Project Structure
- Running Tests
- Writing Tests
- Test Utilities
- Mocking
- Coverage
- Best Practices
- Debugging Tests
- CI/CD Integration
- Troubleshooting
- Resources
Get up and running with tests in 5 minutes! 🚀
cd frontend
npm installnpx playwright installUnit Tests
# Run all tests
npm test
# Watch mode (auto-rerun on file changes)
npm test -- --watch
# With UI (recommended for development)
npm run test:uiE2E Tests
# Make sure your dev server is running first
npm run dev
# In another terminal:
npm run test:e2e
# Or run with interactive UI
npm run test:e2e:uinpm run test:coverage
# Open the HTML report
open coverage/index.html # macOS
xdg-open coverage/index.html # Linux
start coverage/index.html # WindowsCreate a test file next to your component:
// src/components/MySatellite/__tests__/MySatellite.test.jsx
import { describe, it, expect } from 'vitest';
import { screen } from '@testing-library/react';
import { renderWithProviders } from '../../../test/test-utils';
import MySatellite from '../MySatellite';
describe('MySatellite', () => {
it('displays satellite name', () => {
renderWithProviders(
<MySatellite name="ISS" />
);
expect(screen.getByText('ISS')).toBeInTheDocument();
});
});Run it:
npm test -- MySatellite.test.jsx| Command | Description |
|---|---|
npm test |
Run unit tests |
npm run test:ui |
Run tests with UI |
npm run test:coverage |
Run with coverage |
npm run test:e2e |
Run E2E tests |
npm run test:e2e:ui |
Run E2E with UI |
npm run test:e2e:debug |
Debug E2E tests |
Next: Read the sections below for comprehensive testing documentation.
- Vitest - Fast unit test framework for Vite projects
- React Testing Library - Component testing utilities
- Playwright - End-to-end testing framework
- @testing-library/jest-dom - Custom matchers for DOM assertions
- @testing-library/user-event - User interaction simulation
frontend/
├── src/
│ ├── components/
│ │ └── **/__tests__/ # Component tests
│ └── test/
│ ├── setup.js # Test environment setup
│ └── test-utils.jsx # Custom test utilities
├── e2e/ # E2E tests
│ ├── example.spec.js
│ └── satellite-tracking.spec.js
├── vitest.config.js # Vitest configuration
└── playwright.config.js # Playwright configuration
# Run all unit tests
npm test
# Run tests in watch mode
npm test -- --watch
# Run tests with UI
npm run test:ui
# Run with coverage
npm run test:coverage
# Run specific test file
npm test -- src/components/common/__tests__/login.test.jsx# Run all E2E tests
npm run test:e2e
# Run E2E tests with UI (interactive mode)
npm run test:e2e:ui
# Run E2E tests in debug mode
npm run test:e2e:debug
# Run specific test file
npm run test:e2e -- e2e/satellite-tracking.spec.js
# Run tests for specific browser
npm run test:e2e -- --project=chromium
npm run test:e2e -- --project=firefox
npm run test:e2e -- --project=webkit
# Run in headed mode (see the browser)
npm run test:e2e -- --headedCreate test files in __tests__ directories next to your components:
// src/components/common/__tests__/MyComponent.test.jsx
import { describe, it, expect } from 'vitest';
import { screen } from '@testing-library/react';
import { renderWithProviders, userEvent } from '../../../test/test-utils';
import MyComponent from '../MyComponent';
describe('MyComponent', () => {
it('renders correctly', () => {
renderWithProviders(<MyComponent />);
expect(screen.getByText('Hello')).toBeInTheDocument();
});
it('handles user interaction', async () => {
const user = userEvent.setup();
renderWithProviders(<MyComponent />);
await user.click(screen.getByRole('button'));
expect(screen.getByText('Clicked')).toBeInTheDocument();
});
});import { renderWithProviders } from '../../../test/test-utils';
const { store } = renderWithProviders(<MyComponent />, {
preloadedState: {
satellites: {
selected: 'ISS',
list: [{ id: 1, name: 'ISS' }]
}
}
});import { userEvent } from '../../../test/test-utils';
const user = userEvent.setup();
await user.click(screen.getByRole('button'));
await user.type(screen.getByLabelText('Search'), 'satellite');import { waitFor } from '@testing-library/react';
await waitFor(() => {
expect(screen.getByText('Loaded')).toBeInTheDocument();
});// src/components/settings/__tests__/preferences-slice.test.js
import { describe, it, expect } from 'vitest';
import { configureStore } from '@reduxjs/toolkit';
import preferencesReducer, { updatePreference } from '../preferences-slice';
describe('preferences slice', () => {
it('updates preference value', () => {
const store = configureStore({
reducer: { preferences: preferencesReducer },
});
store.dispatch(updatePreference({ key: 'theme', value: 'dark' }));
expect(store.getState().preferences.theme).toBe('dark');
});
});Create test files in the e2e directory:
// e2e/my-feature.spec.js
import { test, expect } from '@playwright/test';
test.describe('My Feature', () => {
test('should work correctly', async ({ page }) => {
await page.goto('/my-feature');
await page.click('button[aria-label="Start"]');
await expect(page.locator('text=Success')).toBeVisible();
});
});Renders a component with Redux, Router, and Theme providers:
import { renderWithProviders } from '../../../test/test-utils';
const { store } = renderWithProviders(<MyComponent />, {
preloadedState: {
satellites: { list: [] }
}
});Creates a mock Socket.IO client for testing:
import { createMockSocket } from '../../../test/test-utils';
const mockSocket = createMockSocket();
mockSocket.emit('connect');
mockSocket.triggerEvent('satellite-tracking', { data: {} });import { vi } from 'vitest';
vi.mock('socket.io-client', () => ({
default: vi.fn(() => mockSocket),
}));import { vi } from 'vitest';
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: async () => ({ data: 'test' }),
})
);Coverage reports are generated in the coverage/ directory:
- HTML Report:
coverage/index.html - Text Summary: Displayed in console
- LCOV:
coverage/lcov.info(for CI/CD)
Current thresholds (configured in vitest.config.js):
- Lines: 70%
- Functions: 70%
- Branches: 70%
- Statements: 70%
- Test Behavior, Not Implementation: Focus on what the component does, not how it does it
- Use Semantic Queries: Prefer
getByRole,getByLabelTextovergetByTestId - Avoid Testing Redux Internals: Test user-facing behavior instead
- Mock External Dependencies: Socket.IO, APIs, browser APIs
- Clean Up: Tests should not affect each other
- Async Operations: Always await async operations
- Accessibility: Use ARIA roles and labels for better testability
# Run with UI (recommended)
npm run test:ui
# Run with --inspect-brk flag
node --inspect-brk ./node_modules/vitest/vitest.mjs run
# Then open chrome://inspect in ChromeThen click on any test to see detailed execution.
# Run in headed mode (see the browser)
npm run test:e2e -- --headed
# Run with Playwright Inspector
npm run test:e2e:debug
# Generate tests with Codegen
npx playwright codegen http://localhost:5173Tests run automatically on:
- Push to
mainordevelopbranches - Pull requests to
mainordevelop
See .github/workflows/tests.yml for CI configuration.
-
Clear node_modules and reinstall:
rm -rf node_modules package-lock.json npm install
-
Update Playwright browsers:
npx playwright install --with-deps
-
Check for stale mocks or test state
- Increase timeout in
playwright.config.js - Check if backend is running (
npm run devin another terminal) - Check network conditions
- Verify selectors are correct
If you encounter issues with React 19:
- This is normal during the RC phase
- Tests should still work correctly
- Ensure all testing libraries are up to date
- Check for console warnings about deprecated features
- Update @testing-library/react when React 19 stable is released
Happy Testing! 🧪✨
For questions or issues with testing setup, check out example tests in src/components/common/__tests__/ and e2e/.