This document outlines the testing approach for the Safe CLI project.
┌─────────────┐
│ E2E CLI │ ← Tests actual CLI interface
├─────────────┤
│ Integration │ ← Tests services with real blockchain
├─────────────┤
│ Unit │ ← Tests individual functions
└─────────────┘
Purpose: Test individual functions and modules in isolation
Location: src/tests/unit/
What they test:
- Utility functions (validation, formatting, errors)
- Individual service methods with mocks
- Pure business logic
Characteristics:
- Fast execution (< 1 second)
- No external dependencies
- Use mocks and stubs
- Run on every commit (pre-commit hook)
- Run in CI on every PR
Example:
// src/tests/unit/utils/validation.test.ts
it('should validate Ethereum address', () => {
expect(validateAddress('0x...')).toBe(undefined)
expect(validateAddress('invalid')).toContain('Invalid')
})Run command:
npm test # Runs all unit tests by defaultPurpose: Test services and workflows with real blockchain and APIs
Location: src/tests/integration/
What they test:
- Full Safe workflow on Sepolia testnet
- ABI fetching from Etherscan
- Transaction Service push/pull/sync
- Complete service interactions
Characteristics:
- Slower execution (5-15 minutes total)
- Requires blockchain access (Sepolia)
- Requires API keys
- Uses real Safe SDK
- Excluded from default test runs
- Run manually or in dedicated CI workflow
Test Suites:
Tests complete Safe CLI workflow:
- Initialize config
- Import wallet
- Create predicted Safe
- Deploy Safe to Sepolia
- Create transaction
- Sign transaction
- Export to JSON
- Import from JSON
- Execute transaction
Requirements:
TEST_WALLET_PK- Funded Sepolia wallet
Tests ABI fetching and contract interaction:
- Deploy Safe
- Fetch ERC20 ABI from Etherscan
- Parse ABI functions
- Build approval transaction
- Create and sign Safe transaction
Requirements:
TEST_WALLET_PK- Funded Sepolia walletETHERSCAN_API_KEY- Etherscan API key
Tests Safe Transaction Service integration:
- Deploy Safe
- Create and sign transaction
- Push to Safe Transaction Service
- Clear local storage
- Pull from service
- Verify sync
Requirements:
TEST_WALLET_PK- Funded Sepolia walletTX_SERVICE_API_KEY- Safe Transaction Service API key
Run commands:
# Run all integration tests
npm test -- integration-*.test.ts
# Run specific integration test
npm test -- integration-full-workflow.test.ts
npm test -- integration-transaction-builder.test.ts
npm test -- integration-transaction-service.test.tsPurpose: Test the actual CLI interface and commands
Location: src/tests/integration/
What they test:
- CLI binary execution
- Command structure and help output
- Error handling
- Environment variable support
- Interactive prompt handling (where possible)
Characteristics:
- Tests CLI as users would use it
- Spawns actual CLI process
- Tests exit codes and output
- Fast execution (< 1 minute)
- No blockchain required for basic tests
Current Coverage:
- ✅ Version command
- ✅ Help output for all commands
- ✅ Error handling
- ✅ Environment variables
⚠️ Interactive flows (limited - requires non-interactive mode)
Run command:
npm test -- e2e-cli.test.tssrc/tests/
├── fixtures/ # Test data and mocks
├── helpers/ # Test utilities
│ ├── cli-test-helper.ts # CLI spawning and testing
│ ├── factories.ts # Test data factories
│ ├── mocks.ts # Mock implementations
│ └── setup.ts # Test setup
├── integration/ # Integration and E2E tests
│ ├── e2e-cli.test.ts # E2E CLI tests
│ ├── integration-full-workflow.test.ts # Full workflow integration
│ ├── integration-transaction-builder.test.ts # ABI/contract integration
│ ├── integration-transaction-service.test.ts # Service integration
│ ├── account.test.ts # Account service integration
│ ├── config.test.ts # Config integration
│ ├── transaction.test.ts # Transaction integration
│ ├── wallet.test.ts # Wallet integration
│ ├── INTEGRATION_README.md # Integration test docs
│ └── test-helpers.ts # Integration test utilities
└── unit/ # Unit tests
├── services/ # Service unit tests
└── utils/ # Utility unit tests
# Run all unit tests (default)
npm test
# Run with UI
npm test:ui
# Run with coverage
npm test -- --coverage
# Run integration tests (requires funded wallet)
export TEST_WALLET_PK="0x..."
export ETHERSCAN_API_KEY="..."
export TX_SERVICE_API_KEY="..."
npm test -- integration-*.test.ts
# Run E2E CLI tests
npm test -- e2e-cli.test.ts
# Run specific test file
npm test -- wallet.test.ts
# Run tests matching pattern
npm test -- accountMain CI Workflow (.github/workflows/ci.yml):
- Runs on every PR
- Executes unit tests only
- Fast feedback (< 2 minutes)
- Must pass for PR merge
Integration Test Workflow (.github/workflows/integration.yml):
- Manual trigger or scheduled
- Runs integration tests on Sepolia
- Runs E2E CLI tests
- Requires secrets configured
- Longer execution (15-20 minutes)
Current thresholds (vitest.config.ts):
- Lines: 30%
- Functions: 69%
- Branches: 85%
- Statements: 30%
Note: Thresholds are set to current levels. As test coverage improves, these should be gradually increased.
- Address:
0x2d5961897847A30559a26Db99789BEEc7AeEd75e - Network: Sepolia testnet only
- Funded via faucets
- Private key stored as GitHub Secret
- Fixtures in
src/tests/fixtures/ - Factories use
@faker-js/fakerfor realistic data - Mocks in
src/tests/helpers/mocks.ts
// ✅ Good: Fast, isolated, mocked dependencies
it('should validate address format', () => {
const result = validateAddress('0x123...')
expect(result).toBeDefined()
})
// ❌ Bad: Slow, external dependencies
it('should fetch data from blockchain', async () => {
const data = await fetchFromBlockchain() // Too slow for unit test
})// ✅ Good: Tests real integration, proper cleanup
it('should create and deploy Safe', async () => {
const safe = await safeService.createSafe(...)
expect(safe).toBeDefined()
// Cleanup in afterEach
}, { timeout: 60000 })
// ❌ Bad: Mocking everything (use unit test instead)
it('should create Safe', async () => {
vi.mock('safe-sdk') // If mocking everything, it's a unit test
})// ✅ Good: Tests actual CLI interface
it('should show help output', async () => {
const result = await cli.exec(['--help'])
expect(result.stdout).toContain('Modern CLI')
})
// ❌ Bad: Testing internal functions (use unit/integration test)
it('should create safe', async () => {
await createSafe() // This is not testing the CLI
})- Increase unit test coverage to 80%+
- Add more integration test scenarios
- Improve E2E CLI test coverage
- Add non-interactive mode to CLI commands (
--yes,--non-interactive) - Add input file support (
--input-file) for automation - Complete E2E CLI workflow testing
- Add performance benchmarks
- Add visual regression testing for UI components
- Add load testing for Transaction Service
- Add security testing for wallet encryption
- Add snapshot testing for command outputs
"TEST_WALLET_PK not set"
- Set environment variable:
export TEST_WALLET_PK="0x..."
"Insufficient funds"
- Fund test wallet on Sepolia: https://sepoliafaucet.com/
"Transaction Service API error"
- Verify
TX_SERVICE_API_KEYis set correctly - Check API key has not expired
"Etherscan API error"
- Verify
ETHERSCAN_API_KEYis set correctly - Check rate limits
"Type check failed"
- Fix TypeScript errors shown in output
- Run
npm run typechecklocally
"Lint failed"
- Run
npm run lint:fixto auto-fix - Fix remaining issues manually
Coverage below threshold
- Add tests for uncovered files
- Focus on
src/services/andsrc/utils/first - Use
npm test -- --coverage --uito see what's not covered
- Vitest Documentation
- Testing Library Best Practices
- Safe SDK Documentation
- See
INTEGRATION_README.mdfor integration test details - See
.husky/README.mdfor pre-commit hook details