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 | 🔲 Planned |
| 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 | 🔲 Planned |
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 the running Next.js dev or preview server. Use it for critical user journeys: sender flow, claim flow, and error paths.
cd frontend
npm install --save-dev @playwright/test
npx playwright install --with-deps chromiumCreate frontend/playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? 'github' : 'list',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});Place test files in frontend/e2e/. MSW service worker is active in the dev server, so network calls are intercepted automatically.
// e2e/send-flow.spec.ts
import { test, expect } from '@playwright/test';
test('sender can complete the send form', async ({ page }) => {
await page.goto('/send');
// connect wallet step
await page.getByRole('button', { name: /connect wallet/i }).click();
await expect(page.getByText(/wallet connected/i)).toBeVisible();
// fill details
await page.getByLabel('Amount').fill('10');
await page.getByRole('button', { name: /continue/i }).click();
// confirm step
await expect(page.getByText(/confirm/i)).toBeVisible();
await page.getByRole('button', { name: /send/i }).click();
// share prompt
await expect(page.getByText(/share this link/i)).toBeVisible();
});// e2e/claim-flow.spec.ts
import { test, expect } from '@playwright/test';
test('recipient can claim funds with a valid token', async ({ page }) => {
await page.goto('/claim/abc123mock');
await expect(page.getByRole('heading', { name: /claim/i })).toBeVisible();
await page.getByRole('button', { name: /claim funds/i }).click();
await expect(page.getByText(/success/i)).toBeVisible();
});# Headless (CI-style)
npx playwright test
# Interactive UI mode
npx playwright test --ui
# Single file
npx playwright test e2e/send-flow.spec.ts
# Debug mode (pauses on each step)
npx playwright test --debugAdd to frontend/package.json:
"scripts": {
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
}The existing frontend-ci.yml workflow will pick up test:e2e once it is added to the test script, or add a dedicated job:
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
working-directory: frontend
- name: Run E2E tests
run: npm run test:e2e
working-directory: frontendStorybook 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 enforces minimum scores for performance, accessibility, best practices, and SEO.
cd frontend
npm install --save-dev @lhci/cliCreate frontend/lighthouserc.cjs:
module.exports = {
ci: {
collect: {
// Build and serve the Next.js app, then audit these URLs
startServerCommand: 'npm run start',
startServerReadyPattern: 'ready on',
url: [
'http://localhost:3000/',
'http://localhost:3000/send',
'http://localhost:3000/claim/abc123',
],
numberOfRuns: 3,
},
assert: {
assertions: {
'categories:performance': ['warn', { minScore: 0.8 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['warn', { minScore: 0.9 }],
'categories:seo': ['warn', { minScore: 0.8 }],
},
},
upload: {
target: 'temporary-public-storage', // replace with LHCI server URL in production
},
},
};Add scripts to frontend/package.json:
"scripts": {
"lhci": "lhci autorun"
}Add a separate workflow or job so Lighthouse audits run after a successful build:
lighthouse:
name: Lighthouse CI
runs-on: ubuntu-latest
needs: build-and-test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
working-directory: frontend
- run: npm run build
working-directory: frontend
- run: npm run lhci
working-directory: frontend
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}| Category | Warning threshold | Error threshold |
|---|---|---|
| Performance | 80 | — |
| Accessibility | — | 90 |
| Best Practices | 90 | — |
| SEO | 80 | — |
Accessibility failures block the CI job (error level). The others emit warnings. Adjust thresholds in lighthouserc.cjs as the app matures.
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
E2E tests require a complete environment:
┌──────────────────────────────────┐
│ Test Client / Web Driver │ (Puppeteer/Playwright)
└──────────────┬───────────────────┘
│
┌──────────────▼───────────────────┐
│ Backend SDK (Test Mode) │ (NestJS on :3001)
└──────────────┬───────────────────┘
│
┌──────────────▼───────────────────┐
│ Stellar Testnet Blockchain │
└──────────────────────────────────┘
# Start the backend in test mode
npm run start:test
# In another terminal, run E2E tests
npm run test:e2e
# Run specific E2E test
npm run test:e2e -- claim-flow.spec.ts-
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