Testing Guide¶
This guide covers testing strategies, IDE setup, and debugging for the Freeze Design webshop.
Test Pyramid¶
┌───────────┐
│ E2E │ ← Few, slow, high confidence
├───────────┤
│Integration│ ← Medium number
├───────────┤
│ Unit │ ← Many, fast, focused
└───────────┘
Backend Testing (Django)¶
Backend tests run on pytest (with pytest-django, pytest-xdist, and factory-boy) — this is what CI runs and what you should run locally. python manage.py test still works but is not the configured path.
Run Tests¶
cd backend
pytest
# Run specific app tests
pytest apps/payments
pytest apps/invoices
pytest apps/orders
# Run specific test modules
pytest apps/payments/tests/test_webhook.py
pytest apps/payments/tests/test_refund_service.py
pytest apps/invoices/tests/test_invoice_service.py
# Run a single test by name
pytest -k "test_webhook_marks_order_paid"
# Extra verbosity
pytest -vv
With Coverage¶
Coverage is configured in backend/pyproject.toml (addopts): parallel (-n auto --dist=loadscope), term-missing report, and a 60% fail-under threshold. Running pytest bare already applies all of this — CI runs the same command.
# Same as CI (coverage + fail-under 60% are applied via pyproject addopts)
pytest
# HTML coverage report
pytest --cov-report=html # open htmlcov/index.html
Test Structure¶
# tests/test_products.py
from django.test import TestCase
from rest_framework.test import APITestCase
class ProductAPITests(APITestCase):
def setUp(self):
self.product = Product.objects.create(
name='Test Product',
base_price=29.95
)
def test_list_products(self):
response = self.client.get('/api/products/')
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['results']), 1)
def test_product_detail(self):
response = self.client.get(f'/api/products/{self.product.slug}/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['name'], 'Test Product')
Frontend Testing¶
Unit Tests (Jest)¶
cd frontend
npm run test
npm run test:coverage
npm run test:watch # Watch mode (auto-rerun)
npm test TeamOrderContext # Specific test file
Coverage thresholds are enforced in frontend/jest.config.js (branches 54%, functions 59%, lines 61%, statements 60%) and are raised incrementally as coverage improves.
Test Structure¶
// Component.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { QuantitySelector } from './QuantitySelector';
describe('QuantitySelector', () => {
it('increments quantity when plus clicked', () => {
const onChange = jest.fn();
render(<QuantitySelector quantity={1} onQuantityChange={onChange} />);
fireEvent.click(screen.getByLabelText('Verhoog aantal'));
expect(onChange).toHaveBeenCalledWith(2);
});
it('disables decrement at minimum', () => {
render(<QuantitySelector quantity={1} onQuantityChange={() => {}} min={1} />);
expect(screen.getByLabelText('Verlaag aantal')).toBeDisabled();
});
});
Running Specific Tests¶
# Single test file
npm test TeamOrderContext.test.tsx
# Single test case
npm test -- -t "should save team order to context"
# All tests matching pattern (Jest 30: --testPathPatterns, plural)
npm test -- --testPathPatterns=contexts
E2E Testing (Playwright)¶
Run Tests¶
cd frontend
npm run test:e2e # All E2E tests (headless)
npm run test:e2e:headed # See browser while testing
npm run test:e2e:ui # Interactive Playwright UI
Running Specific E2E Tests¶
# Single test file
npx playwright test team-order-designer-flow.spec.ts
# Single test case
npx playwright test -g "should preserve team order"
# By tag
npx playwright test --grep="@smoke"
# Specific browser
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=webkit
Test Structure¶
// e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Checkout Flow', () => {
test('complete order', async ({ page }) => {
await page.goto('/products/classic-t-shirt');
await page.click('[data-testid="color-swatch-1"]');
await page.click('[data-testid="size-option-3"]');
await page.click('[data-testid="add-to-cart"]');
await page.click('[data-testid="checkout-button"]');
await page.fill('[name="email"]', 'test@example.com');
await page.click('[data-testid="submit-order"]');
await expect(page.locator('.order-confirmation')).toBeVisible();
});
});
Viewing E2E Results¶
- HTML Report: Auto-opens after test run, or manually:
npx playwright show-report - Trace Viewer: For failed tests:
npx playwright show-trace trace.zip
VS Code Setup¶
Recommended Extensions¶
| Extension | ID | Purpose |
|---|---|---|
| Jest | Orta.vscode-jest |
Inline test results, coverage, run/debug tests |
| Playwright Test | ms-playwright.playwright |
Run/debug E2E tests, record tests, trace viewer |
Install via terminal:
Configure Jest Extension¶
Add to .vscode/settings.json:
VS Code Shortcuts¶
Unit Tests (Jest):
- Hover over test and click play button to run
- Right-click test for "Debug Test"
- Cmd+Shift+P then "Jest: Toggle Coverage"
E2E Tests (Playwright):
- Click play button in gutter next to test
- Right-click for "Debug Test"
- Cmd+Shift+P then "Test: Record New"
Debugging Tests¶
Debug Unit Tests (Jest)¶
Debug E2E Tests (Playwright)¶
CI/CD Integration¶
Tests run automatically via GitHub Actions on every push to main/staging and on every PR against those branches. See .github/workflows/ci.yml.
CI Jobs on Every PR / Push¶
- backend-test — flake8, migration checks, full pytest suite (coverage ≥60%)
- frontend-test — TypeScript check, ESLint, full Jest suite (coverage thresholds from
jest.config.js) - frontend-build — Next.js production build
- e2e-smoke-test — Playwright
@smoketests - e2e-visual-test — Playwright visual regression tests
- api-types-drift ("API types drift check") — regenerates the frontend API types from the backend OpenAPI schema and fails if the committed types drifted
Runs only on the weekly schedule or manual workflow_dispatch:
- e2e-full-test — full Playwright suite
- e2e-admin-test — admin + multi-zone E2E (
chromium-adminproject: admin login via TOTPstorageState+ designer/multi-zone fixtures) - security-scan-dependencies — npm audit, pip-audit, Snyk
- docker-build — Docker image build validation + Trivy scans
- seed data export/import round-trip test
On pushes to staging, the deploy job runs after backend-test, frontend-test, frontend-build, e2e-smoke-test, and e2e-visual-test are green and deploys to staging. Staging→main promotion PRs skip the heavy jobs (the code was already tested on staging); check-base is the only required check on main.
E2E Strategy in CI¶
| Trigger | Tests Run | Browsers |
|---|---|---|
Pull requests & pushes (main/staging) |
Smoke tests (@smoke tag) + visual regression |
Chromium |
Weekly schedule or manual dispatch (run_full_e2e) |
Full E2E suite (all tests) | Chromium |
Locally, npm run test:e2e runs all configured Playwright projects (chromium, firefox, webkit, mobile, visual).
Pre-push Hook¶
The pre-push git hook (.githooks/pre-push) runs a backend test subset (requires local PostgreSQL on 5432 and Redis on 6379, e.g. via docker-compose.dev.yml), the TypeScript check, a non-blocking CodeRabbit review, and the Jest suite with coverage. If checks fail, the push is blocked. Never bypass with --no-verify.
Test File Locations¶
backend/
├── apps/
│ ├── payments/tests/ # Payment tests
│ │ ├── test_models.py # Payment, Refund, AuditLog model tests
│ │ ├── test_services.py # PaymentService unit tests
│ │ ├── test_views.py # API endpoint tests
│ │ ├── test_webhook.py # Mollie webhook handler tests
│ │ ├── test_refund_service.py # Refund creation/validation
│ │ ├── test_refund_webhook.py # Refund webhook processing
│ │ ├── test_retry.py # Payment retry tests
│ │ └── test_audit.py # Audit logging tests
│ ├── invoices/tests/ # Invoice tests
│ │ ├── test_invoice_service.py # Generation, numbering, idempotency
│ │ └── test_invoice_views.py # Download authorization tests
│ └── orders/tests/
│ └── test_refund_admin.py # Admin refund action tests
frontend/
├── __tests__/ # Unit tests
│ ├── contexts/
│ │ └── TeamOrderContext.test.tsx
│ └── README.md
├── e2e/ # E2E tests
│ ├── flows/
│ │ └── customer-journey.spec.ts # Full payment journey
│ ├── team-order-designer-flow.spec.ts
│ ├── anonymous-team-order.spec.ts
│ └── ...
├── coverage/ # Coverage reports (generated)
├── playwright-report/ # E2E reports (generated)
└── test-results/ # E2E artifacts (generated)
Troubleshooting¶
"Cannot find module '@testing-library/react'"¶
"Playwright browsers not installed"¶
Tests timing out¶
Increase timeout in test file:
Port 3000 already in use (E2E tests)¶
Coverage not showing¶
Quick Reference¶
| Task | Command |
|---|---|
| Backend (pytest) | |
| All backend tests | cd backend && pytest |
| Payment tests | pytest apps/payments |
| Invoice tests | pytest apps/invoices |
| Webhook tests | pytest apps/payments/tests/test_webhook.py |
| Frontend (Jest) | |
| All unit tests | npm test |
| Watch mode | npm run test:watch |
| With coverage | npm run test:coverage |
| Frontend (Playwright) | |
| All E2E tests | npm run test:e2e |
| Headed mode | npm run test:e2e:headed |
| Interactive UI | npm run test:e2e:ui |
| Debug E2E | npx playwright test --debug |
| Smoke tests only | npx playwright test --grep="@smoke" |
| View report | npx playwright show-report |