This document provides comprehensive testing guidelines for the Bridgelet project, covering the frontend web app, mobile app, and SDK integration. It includes testing strategies, tooling setup, and best practices for contributors.
- Testing Philosophy
- Test Categories
- Test Coverage Overview
- Frontend Testing
- Mobile Testing
- Unit Tests
- Integration Tests
- End-to-End (E2E) Tests
- Manual Testing
- Testing Against Live Testnet
- Testing with Mock Data
- Test Data Requirements
- Release Testing Checklist
- Troubleshooting Guide
- Contributing Tests
The Bridgelet testing strategy is built on multiple layers of validation to ensure reliability, security, and correctness of the ephemeral account system. Our approach emphasizes:
- Security First: All financial interactions are thoroughly tested to prevent vulnerabilities
- Deterministic Testing: Tests should be reproducible and not dependent on external state
- Clear Boundaries: Each test category has a specific scope and responsibility
- Coverage Goals: Aim for >80% code coverage on critical paths (SDK, smart contracts)
- Representative Data: Test data should reflect real-world usage patterns
The table below maps each part of the codebase to the testing tools and the current status of that coverage.
| Area | Tool(s) | Scope | Status |
|---|---|---|---|
| Frontend components | Vitest + React Testing Library | Unit / component rendering | 🔲 Planned |
| Frontend API layer | MSW v2 | Network mock in dev and tests | ✅ Mock handlers implemented |
| Frontend E2E flows | Playwright | Full browser user journeys | ✅ Configured (smoke suite) |
| Frontend visual regression | Storybook + Chromatic | Component story snapshots | 🔲 Planned |
| Frontend performance | Lighthouse CI | Core Web Vitals, accessibility score | 🔲 Planned |
| Mobile unit tests | Jest + jest-expo | Component and utility logic | |
| SDK unit tests | Jest | Core account / payment logic | 🔲 Planned |
| SDK integration tests | Jest + Stellar testnet | Blockchain interactions | 🔲 Planned |
| E2E system tests | Playwright | End-to-end cross-layer flows | ✅ Harness in place (e2e/) |
Legend: ✅ In place ·
Coverage thresholds are not yet enforced in CI. The target is ≥ 80 % on critical paths once the unit test suites are stable. See BRANCH_PROTECTION.md for the CI gate policy.
The frontend lives in frontend/ and is a Next.js 16 App Router application written in TypeScript. Its testing stack is being built incrementally; this section documents both what is already in place and what to add next.
Mock Service Worker (MSW) v2 intercepts fetch and XHR calls at the network level. The frontend ships a fully implemented set of handlers used for local development and, once a test runner is wired up, for component and integration tests as well.
| Handler file | Method + URL | What it mocks |
|---|---|---|
mocks/handlers/accounts.ts |
POST /api/accounts |
Creates a fake ephemeral Stellar account; 300 ms delay |
mocks/handlers/claims.ts |
POST /claims/redeem |
Returns a stubbed claim/sweep response |
mocks/handlers/horizon.ts |
GET https://horizon-testnet.stellar.org/fee_stats |
Testnet fee statistics |
mocks/handlers/horizon.ts |
GET https://horizon-testnet.stellar.org/accounts/:id |
Testnet account with 10 000 XLM balance |
The worker is not started automatically. Add the following to app/layout.tsx (or your top-level client component) to activate it in development:
// app/layout.tsx
if (process.env.NODE_ENV === 'development') {
const { initMocks } = await import('@/mocks');
await initMocks();
}initMocks() is a no-op in SSR contexts (typeof window === 'undefined' guard is already in place).
When Vitest (or Jest) is added to the frontend, use msw/node for a server-side handler instead of the browser service worker:
// tests/setup.ts
import { server } from '@/mocks/server'; // create this file — see below
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());Create frontend/mocks/server.ts alongside browser.ts:
// frontend/mocks/server.ts
import { setupServer } from 'msw/node';
import { accountHandlers } from './handlers/accounts';
import { claimsHandlers } from './handlers/claims';
import { horizonHandlers } from './handlers/horizon';
export const server = setupServer(
...accountHandlers,
...claimsHandlers,
...horizonHandlers,
);To override a handler for a single test:
import { http, HttpResponse } from 'msw';
import { server } from '@/mocks/server';
it('shows an error when account creation fails', async () => {
server.use(
http.post('/api/accounts', () =>
HttpResponse.json({ error: 'Service unavailable' }, { status: 503 }),
),
);
// render and assert...
});frontend/mocks/browser.ts uses horizonHandlers but the import line is missing. Add it:
import { horizonHandlers } from './handlers/horizon';The frontend does not yet have a unit test runner configured. The recommended setup uses Vitest (fast, native ESM, shares the TypeScript config) together with React Testing Library.
cd frontend
npm install --save-dev vitest @vitejs/plugin-react jsdom \
@testing-library/react @testing-library/user-event \
@testing-library/jest-domAdd a vitest.config.ts at frontend/:
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./tests/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
thresholds: { lines: 80, branches: 80, functions: 80 },
exclude: ['**/node_modules/**', '**/mocks/**', '**/*.d.ts'],
},
},
resolve: {
alias: { '@': path.resolve(__dirname, '.') },
},
});Add the test script to frontend/package.json:
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}# Single run (used in CI)
npm test
# Watch mode for local development
npm run test:watch
# With coverage report
npm run test:coveragePlaywright drives a real browser against http://localhost:3000. Use it for critical user journeys: navigation, sender flow, claim flow, and error paths.
| Path | Purpose |
|---|---|
frontend/playwright.config.ts |
Base config (baseURL → localhost:3000) |
frontend/e2e/*.spec.ts |
Browser specs (smoke suite today) |
docs/testing/E2E_GUIDELINES.md |
Selector and isolation standards |
cd frontend
npx playwright install --with-deps chromiumcd frontend
# Headless — starts `next dev` automatically (or reuses a running server)
npm run test:e2e
# Interactive UI mode
npm run test:e2e:ui
# Single file
npx playwright test e2e/home.spec.ts
# Debug mode (pauses on each step)
npx playwright test --debugLocally, webServer runs npm run dev and reuses an existing server when present. In CI (CI=true), it runs npm run start against the built Next.js app (the workflow runs npm run build first).
Place new specs in frontend/e2e/. Prefer role/label/data-testid selectors over CSS classes — see E2E Guidelines.
// e2e/home.spec.ts
import { test, expect } from '@playwright/test';
test('loads the homepage', async ({ page }) => {
await page.goto('/');
await expect(
page.getByRole('heading', { name: /bridgelet payment flows/i }),
).toBeVisible();
});Deeper send/claim journeys that depend on wallets or the API should stay deterministic (mocked routes or sandbox pages) so CI does not hit live Stellar.
.github/workflows/frontend-ci.yml builds the app, installs Chromium, then runs npm run test:e2e with CI=true so Playwright serves the production build via next start. On failure, the Playwright HTML report is uploaded as an artifact.
Storybook documents UI components in isolation and enables visual regression testing via Chromatic.
cd frontend
npx storybook@latest init
# Choose: Next.js, TypeScript, no ESLint extensionThis creates .storybook/ with main.ts and preview.ts, and adds Storybook scripts to package.json.
Create a story file alongside each component, e.g. components/share-prompt.stories.tsx:
import type { Meta, StoryObj } from '@storybook/react';
import { SharePrompt } from './share-prompt';
const meta: Meta<typeof SharePrompt> = {
title: 'Components/SharePrompt',
component: SharePrompt,
parameters: { layout: 'centered' },
};
export default meta;
type Story = StoryObj<typeof SharePrompt>;
export const Default: Story = {
args: {
claimUrl: 'https://bridgelet.app/claim/abc123',
},
};
export const LongUrl: Story = {
args: {
claimUrl: 'https://bridgelet.app/claim/' + 'x'.repeat(64),
},
};Cover the main components:
| Component | Story variants to add |
|---|---|
ClaimStatusCard |
loading, success, expired, error |
SharePrompt |
default, long URL, copied state |
WalletConnect |
disconnected, connecting, connected |
SendForm steps |
connect, details, confirm |
Logo |
light, dark |
PageShell |
default layout |
# Start dev server at http://localhost:6006
npm run storybook
# Build a static version
npm run build-storybooknpm install --save-dev chromatic
npx chromatic --project-token=<your-token>Add to CI:
- name: Publish to Chromatic
run: npx chromatic --project-token=${{ secrets.CHROMATIC_PROJECT_TOKEN }}
working-directory: frontendChromatic compares component snapshots on each PR. Reviewers approve or reject visual diffs in the Chromatic dashboard before merging.
Lighthouse CI runs Google Lighthouse against the built app on every pull request and push to main, enforcing strict minimum scores for performance, accessibility, and best practices.
The configuration is already present in frontend/lighthouserc.js and the workflow in .github/workflows/lighthouse-ci.yml.
You can run the Lighthouse checks locally before pushing to catch regressions early:
cd frontend
npm install
npm run build
npm run lhci| Category | Error threshold (Minimum Score) |
|---|---|
| Performance | 85 |
| Accessibility | 95 |
| Best Practices | 90 |
Any score dropping below these thresholds will block the CI job (fail the build). If you see a CI failure, review the output logs or the temporary public storage link for a detailed Lighthouse report to fix the issues.
The mobile app lives in mobile/ and uses Expo + React Native. Jest is already configured.
cd mobile
npm test # single run with coverage
npm test -- --watch # watch mode
npm test -- --testPathPattern="ComponentName" # single filemobile/jest.config.js uses the jest-expo preset which handles Babel transforms for React Native packages. Coverage is collected from all *.ts and *.tsx files.
// mobile/jest.config.js (current)
module.exports = {
preset: 'jest-expo',
collectCoverage: true,
collectCoverageFrom: [
'**/*.{ts,tsx}',
'!**/node_modules/**',
'!**/vendor/**',
],
};Place test files next to the source files or in __tests__/ directories:
mobile/
app/
(onboarding)/
index.tsx
__tests__/
index.test.tsx
components/
my-component.tsx
my-component.test.tsx
Bridgelet employs a multi-tiered testing strategy across the frontend, mobile, and SDK layers:
┌─────────────────────────────────────────────────────────────┐
│ End-to-End Tests (E2E) │
│ Full user flows across all systems │
└─────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────┐
│ Integration Tests (SDK + Blockchain) │
│ Tests SDK with real/mock Stellar interactions │
└─────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────┐
│ Unit Tests (Isolated) │
│ Individual functions, methods, and components │
└─────────────────────────────────────────────────────────────┘
Scope: Individual functions and methods in isolation
Location: bridgelet-sdk/src/**/*.spec.ts
Coverage Areas:
- Account creation logic
- Payment processing
- Claim validation
- Cryptographic operations
- Data validation and sanitization
- Error handling paths
Tools & Frameworks:
- Jest for test runner
- TypeScript for type safety
- Mocking libraries (ts-mockito, jest.mock)
Example Test Structure:
describe("AccountService", () => {
describe("createEphemeralAccount", () => {
it("should generate a valid Stellar keypair", () => {
// Test keypair generation
});
it("should reject invalid configuration", () => {
// Test input validation
});
it("should handle cryptographic errors gracefully", () => {
// Test error handling
});
});
});Scope: SDK interactions with Stellar blockchain (testnet)
Location: bridgelet-sdk/tests/integration/**
Coverage Areas:
- Account creation on testnet
- Smart contract interactions
- Payment submission and validation
- Fund sweeping operations
- Testnet state transitions
- Transaction confirmation
Setup Requirements:
- Testnet node access
- Test account funding
- Smart contract deployment
- Environment variables for testnet RPC
Scope: Complete user workflows from claim to fund sweep
Location: bridgelet-sdk/tests/e2e/**
Coverage Areas:
- Full payment initialization workflow
- Account claiming process
- Fund distribution and sweep
- Expiration and recovery flows
- Multi-recipient scenarios
- Edge cases and error recovery
Environment:
- Testnet for blockchain operations
- Test database with clean state
- Mock payment processor (optional)
Scope: User acceptance testing and exploratory testing
When to Use:
- Before release candidate creation
- Testing new features requiring user interaction
- Exploratory testing for edge cases
- UI/UX validation
- Manual security review
Manual Test Scenarios:
- Account creation and ownership verification
- Claim flow with various wallet types
- Payment settlement timing
- Fund recovery after expiration
- Error message clarity
-
Use descriptive names:
it("should return 404 when account does not exist", () => { // Implementation });
-
Follow AAA Pattern (Arrange, Act, Assert):
it("should calculate sweep amount correctly", () => { // Arrange const balance = 1000; const fee = 0.01; // Act const sweepAmount = calculateSweepAmount(balance, fee); // Assert expect(sweepAmount).toBe(999.99); });
-
Mock external dependencies:
it("should log account creation", () => { // Mock the logger const loggerSpy = jest.spyOn(logger, "info"); createAccount(); expect(loggerSpy).toHaveBeenCalledWith("Account created"); });
# Run all tests
npm test
# Run tests in watch mode
npm test -- --watch
# Run specific test file
npm test -- AccountService.spec.ts
# Run with coverage report
npm test -- --coverage/// To be updated later
/// Not enough information
The integration test harness lives in e2e/ at the repository root and
covers the full send → claim → sweep user journey spanning the frontend (this
repo), bridgelet-sdk, and (optionally) a bridgelet-core testnet contract
deployment.
| Test file | Scenarios covered |
|---|---|
e2e/tests/happy-path.spec.ts |
Send flow completion; pending claim view; full send → claim → sweep |
e2e/tests/failure-paths.spec.ts |
Expired token (401); already-claimed (409); invalid token (400); network error; redemption failure (500) |
┌──────────────────────────────────┐
│ Playwright (headless Chromium) │
└──────────────┬───────────────────┘
│ HTTP via page.route() or MSW
┌──────────────▼───────────────────┐
│ Next.js dev server (:3000) │
│ ↳ MSW intercepts API calls │ (default / mocked mode)
└──────────────┬───────────────────┘
│ (optional, E2E_USE_MOCKS=false)
┌──────────────▼───────────────────┐
│ bridgelet-sdk (:3001) │
└──────────────┬───────────────────┘
│ (optional, full cross-layer)
┌──────────────▼───────────────────┐
│ Stellar Testnet Blockchain │
└──────────────────────────────────┘
# 1. Install e2e dependencies and Playwright browser
cd e2e
npm install
npx playwright install --with-deps chromium
# 2. Run all tests (starts the Next.js dev server automatically)
npm test
# 3. Interactive UI mode (shows browser timeline, traces, etc.)
npm run test:ui
# 4. Debug a specific test
npm run test:debug -- tests/failure-paths.spec.ts
# 5. View the HTML report from the last run
npm run test:reportThe test runner starts cd ../frontend && npm run dev before executing tests.
If you already have the dev server running on localhost:3000, it will be reused.
For full-stack journeys that need the SDK backend and Stellar testnet, start those services separately and keep browser specs deterministic (mocks/sandbox routes). See Playwright E2E Tests.
# Start bridgelet-sdk locally (see bridgelet-sdk README for full setup)
cd /path/to/bridgelet-sdk && npm run start:dev # listens on :3001
# Run e2e tests against the real backend
cd e2e
E2E_USE_MOCKS=false E2E_API_BASE_URL=http://localhost:3001 npm test| Variable | Default | Purpose |
|---|---|---|
E2E_USE_MOCKS |
true |
Set to false to use a real bridgelet-sdk instance |
E2E_BASE_URL |
http://localhost:3000 |
Frontend URL targeted by Playwright |
E2E_API_BASE_URL |
(none) | bridgelet-sdk base URL (only used when E2E_USE_MOCKS=false) |
E2E tests run in a dedicated, scheduled GitHub Actions workflow (.github/workflows/e2e.yml) rather than on every PR — cross-repo setup cost makes gating every commit impractical. The workflow:
- Runs daily at 06:00 UTC.
- Can be triggered manually from the Actions tab (supports
use_mocksinput). - Also triggers automatically on PRs that modify files under
e2e/,frontend/app/,frontend/components/,frontend/lib/, orfrontend/mocks/.
See the full workflow at .github/workflows/e2e.yml.
For full setup instructions including testnet account funding and contract deployment, see e2e/README.md.
-
Testnet Account Setup:
# Fund a testnet account using friendbot curl "https://friendbot.stellar.org?addr=YOUR_PUBLIC_KEY"
-
Environment Configuration:
# .env.testnet STELLAR_NETWORK=testnet STELLAR_RPC_URL=https://soroban-testnet.stellar.org STELLAR_ACCOUNT_SECRET=SBBB... TESTNET_FUNDING_AMOUNT=1000 -
Smart Contract Deployment:
# Deploy contracts to testnet (from bridgelet-core) ./scripts/deploy-testnet.sh
Phase 1: Smoke Tests
- Quick validation that basic operations work
- Account creation
- Simple fund transfers
Phase 2: Functional Tests
- Complete workflow tests
- Multiple payment scenarios
- Expiration handling
Phase 3: Load Tests (optional)
- Multiple concurrent transactions
- Performance baseline establishment
- Bridgelet SDK Repository - Test examples
- Stellar Testing Documentation
- Jest Documentation
- Soroban Testing Guide
- Test Issues: Create an issue with
[test]label - Questions: Post in Discussions
- Security: See SECURITY.md for responsible disclosure
Last Updated: June 2026 Maintained By: Bridgelet Core Team