This document outlines the testing strategy and setup for the STO Web Client.
Test Coverage: 0%
Test Framework: Not configured
Priority: High - Critical gap
yarn add -D jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdomCreate jest.config.js:
const nextJest = require('next/jest')
const createJestConfig = nextJest({
dir: './',
})
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.stories.{ts,tsx}',
'!src/**/__tests__/**',
],
}
module.exports = createJestConfig(customJestConfig)Create src/lib/utils/__tests__/validation.test.ts:
import { isValidAddress, validateAddress, validateAmount } from '../validation';
describe('validation utilities', () => {
describe('isValidAddress', () => {
it('should return true for valid addresses', () => {
expect(isValidAddress('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb')).toBe(true);
});
it('should return false for invalid addresses', () => {
expect(isValidAddress('invalid')).toBe(false);
expect(isValidAddress('')).toBe(false);
});
});
describe('validateAddress', () => {
it('should return address for valid input', () => {
const address = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
expect(validateAddress(address)).toBe(address);
});
it('should throw for invalid address', () => {
expect(() => validateAddress('invalid')).toThrow('Invalid Ethereum address');
});
});
describe('validateAmount', () => {
it('should return number for valid amount', () => {
expect(validateAmount('100')).toBe(100);
expect(validateAmount(100)).toBe(100);
});
it('should throw for invalid amount', () => {
expect(() => validateAmount(-1)).toThrow('Amount must be a positive number');
expect(() => validateAmount('invalid')).toThrow();
});
});
});Create src/components/token/__tests__/GetTokenBalance.test.tsx:
import { render, screen, waitFor } from '@testing-library/react';
import { useAccount, useContractRead } from 'wagmi';
import GetTokenBalance from '../getTokenBalance';
jest.mock('wagmi');
describe('GetTokenBalance', () => {
const mockUseAccount = useAccount as jest.MockedFunction<typeof useAccount>;
const mockUseContractRead = useContractRead as jest.MockedFunction<typeof useContractRead>;
beforeEach(() => {
jest.clearAllMocks();
});
it('should show connect message when wallet not connected', () => {
mockUseAccount.mockReturnValue({
address: undefined,
isConnected: false,
} as any);
mockUseContractRead.mockReturnValue({
data: undefined,
isLoading: false,
isError: false,
} as any);
render(<GetTokenBalance />);
expect(screen.getByText(/connect your wallet/i)).toBeInTheDocument();
});
it('should display token balance when connected', async () => {
mockUseAccount.mockReturnValue({
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
isConnected: true,
} as any);
mockUseContractRead.mockReturnValue({
data: { toString: () => '1000' },
isLoading: false,
isError: false,
} as any);
render(<GetTokenBalance />);
await waitFor(() => {
expect(screen.getByText('1000')).toBeInTheDocument();
});
});
});yarn add -D mswimport { render, screen, waitFor } from '@testing-library/react';
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import IssueToken from '../components/token/issueToken';
// Mock wagmi hooks
jest.mock('wagmi');
const server = setupServer(
// Add request handlers
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('IssueToken Integration', () => {
it('should submit form and execute transaction', async () => {
// Test implementation
});
});yarn add -D @playwright/test
npx playwright installCreate 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,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});Create e2e/wallet-connection.spec.ts:
import { test, expect } from '@playwright/test';
test('should connect wallet', async ({ page }) => {
await page.goto('/');
// Click connect button
await page.click('text=Connect Wallet');
// Wait for wallet modal
await expect(page.locator('[data-testid="wallet-modal"]')).toBeVisible();
// Select MetaMask
await page.click('text=MetaMask');
// Verify connection
await expect(page.locator('text=Connected')).toBeVisible();
});- Unit Tests: 80%+ coverage for utilities and hooks
- Component Tests: 70%+ coverage for components
- Integration Tests: Critical user flows
- E2E Tests: Main user journeys
# Run all tests
yarn test
# Run in watch mode
yarn test:watch
# Run with coverage
yarn test:coverage
# Run E2E tests
yarn test:e2e- Test behavior, not implementation - Focus on what the code does, not how
- Use descriptive test names - Clear test descriptions
- Keep tests isolated - Each test should be independent
- Mock external dependencies - Don't make real API calls in tests
- Test edge cases - Include error cases and boundary conditions
- Maintain test data - Use factories or fixtures for test data
- Set up Jest and React Testing Library
- Write tests for utility functions
- Write tests for custom hooks
- Write tests for components
- Set up E2E testing
- Add to CI/CD pipeline