Overview
At Formbricks, we maintain a rigorous testing strategy to ensure high-quality code and reliable functionality. Our testing approach is standardized and integrated into our development workflow.Testing Structure
Unit Testing with Vitest
We use Vitest as our primary testing framework. All unit tests follow these conventions:-
File Location and Naming
- Test files are co-located with the source files they test
- Test files use the
.test.tsextension (e.g.,utils.test.tstestsutils.ts)
-
Test Organization
-
Coverage
- Coverage is measured, not targeted: confidence over coverage, and no test exists to move the
number (see
AGENTS.md, “Testing Guidelines”) - The repo enforces exactly one Vitest threshold:
modules/auth/lib/**at 80% (apps/web/vite.config.mts). Everything else is reported —pnpm test:coverageemits lcov and the SonarQube workflow uploads it, with the quality gate configured in SonarQube, not here - Coverage is tracked using the V8 provider
- Coverage reports include:
- Text summaries
- HTML reports
- LCOV reports
- Coverage is measured, not targeted: confidence over coverage, and no test exists to move the
number (see
End-to-End Testing with Playwright
E2E tests are located inapps/web/playwright/ and focus on critical user workflows. AGENTS.md is the
source of truth for when one is warranted; the short version:
- Level. Prove a behavior at the cheapest level that can fail on it. A feature’s happy path or a
journey across several surfaces is E2E. Business logic, invariants and anything pure is a unit test on
the
.ts. A route’s authorization, response shape or query scoping is a unit or integration test on that route. - Unit of coverage. One spec per feature area, not per ticket and not per component. Every feature
area ships a happy-path spec, and a new spec file is for an area that has none. Inside an area that
already has one, the level still follows the behavior: journey behavior extends that spec, logic goes
to a
.tsunit test, UI detail goes to manual QA (see 3) — never a second spec file. - Neither is an option. A granular UI detail inside a covered feature — a label, a breadcrumb, an ARIA attribute, a keystroke inside one widget, a field’s validation message — is verified manually and recorded in the PR. It does not get a spec.
- Cost. Every spec is paid on every PR by everyone, forever. The Playwright job is the critical path of the PR gate and its wall clock can never drop below its slowest single test, so a new spec has to buy risk coverage that nothing cheaper can.
Testing Setup
Configuration
The web app’s Vitest configuration (apps/web/vite.config.mts) includes:
Test Utilities
Common test utilities are available invitestSetup.ts:
- Mock implementations for commonly used functions
- Test lifecycle hooks (beforeEach, afterEach)
- Validation test helpers
Best Practices
-
Test Independence
-
Mocking
- Use Vitest’s built-in mocking utilities
- Mock external dependencies and services
- Example:
-
Assertions
- Write clear, specific assertions
- Test both success and error cases
- Example:
Quality Assurance Process
-
Continuous Integration
- Automated test suite execution on pull requests
- Coverage reports generation
- Test results reporting
-
New Features
- Must include unit tests for the logic they add
- A new user-facing feature area must include one happy-path E2E spec. Inside an area that already has one, the level follows the behavior: journey behavior extends that spec, logic goes to a unit test, UI detail is verified manually
- Must not add tests whose only purpose is to move a coverage metric