Frontend Testing
Philosophy
Section titled “Philosophy”Granit-front uses a mixed testing approach:
- Unit tests for pure functions (
@granit/utils,@granit/logger) - React integration tests for hooks and contexts (
@granit/react-authentication,@granit/react-authorization) - Coverage ≥ 80% on all new code — blocking requirement
Test stack
Section titled “Test stack”| Tool | Role |
|---|---|
| Vitest 3 | Test runner, native ESM compatible |
| @testing-library/react 16 | render(), renderHook(), screen, waitFor() |
| jsdom 26 | DOM environment for React tests |
| v8 (coverage) | Coverage provider — lcov, HTML, Cobertura reports |
Configuration
Section titled “Configuration”The root vitest.config.ts configures the entire workspace:
export default defineConfig({ test: { globals: true, environment: 'jsdom', include: [ 'packages/@granit/*/src/**/*.test.ts', 'packages/@granit/*/src/**/*.test.tsx', ], coverage: { provider: 'v8', reporter: ['text', 'lcov', 'html', 'cobertura'], reportsDirectory: './coverage', include: ['packages/@granit/*/src/**/*.{ts,tsx}'], exclude: ['**/*.d.ts', '**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts'], }, },});Commands
Section titled “Commands”pnpm test # Watch mode — developmentpnpm test:coverage # Single run with coveragepnpm --filter @granit/react-authentication test # Target a specific packageFile structure
Section titled “File structure”Tests are co-located with sources in src/__tests__/:
packages/@granit/react-authentication/└── src/ ├── index.ts ├── keycloak-core.ts ├── use-auth-context.ts ├── mock-provider.tsx └── __tests__/ ├── keycloak-core.test.tsx ├── use-auth-context.test.tsx └── mock-provider.test.tsxNaming conventions
Section titled “Naming conventions”| Element | Convention | Example |
|---|---|---|
| Test file | <module>.test.ts(x) | logger.test.ts |
describe block | Function or hook name | describe('createLogger', …) |
it block | Expected behavior in English | it('should log warn in production', …) |
Mock patterns
Section titled “Mock patterns”Spy on console (@granit/logger)
Section titled “Spy on console (@granit/logger)”const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
logger.warn('Token expired');
expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining('[MyApp]'), 'Token expired',);Mock a module (@granit/api-client)
Section titled “Mock a module (@granit/api-client)”vi.mock('axios', () => ({ default: { create: vi.fn(() => ({ interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() }, }, })), },}));Hoisted mocks with mutable state (@granit/react-authentication)
Section titled “Hoisted mocks with mutable state (@granit/react-authentication)”const mockKeycloak = vi.hoisted(() => ({ init: vi.fn().mockResolvedValue(true), authenticated: true, token: 'mock-token', loadUserInfo: vi.fn().mockResolvedValue({ sub: '123', name: 'Test' }), updateToken: vi.fn().mockResolvedValue(true),}));
vi.mock('keycloak-js', () => ({ default: vi.fn(() => mockKeycloak),}));Test a React hook with context
Section titled “Test a React hook with context”import { renderHook } from '@testing-library/react';
const wrapper = ({ children }: { children: React.ReactNode }) => ( <AuthContext.Provider value={mockAuthValue}> {children} </AuthContext.Provider>);
const { result } = renderHook(() => useAuth(), { wrapper });expect(result.current.authenticated).toBe(true);Best practices
Section titled “Best practices”- One
describeper exported function/hook — no flat tests - Self-contained tests — each
itis independent, no inter-test dependencies mockImplementation(() => {})on console spies to suppress noisevi.clearAllMocks()inbeforeEachfor clean state- Exact assertions — prefer
toEqual()overtoMatchObject()when possible act()andwaitFor()for all async tests involving React hooks- No shared test utilities — each test file is self-sufficient
Coverage reports
Section titled “Coverage reports”| Format | File | Usage |
|---|---|---|
| Text | terminal | Developer (quick summary) |
| HTML | coverage/index.html | Developer (detailed exploration) |
| LCOV | coverage/lcov.info | SonarQube |
| Cobertura | coverage/cobertura-coverage.xml | CI (PR coverage report) |
See also
Section titled “See also”- Frontend Quick Start — integrate granit-front
- Frontend CI/CD — pipeline and quality gates
- Frontend SDK Reference — package documentation