Testing Guide - Comprehensive Testing Strategy¶
Testing Philosophy¶
Core Principles:¶
- ✅ No Mocks in Integration Tests - реальные services, database, blockchain
- ✅ Test Pyramid - много unit, средне integration, мало E2E
- ✅ Fast Feedback - unit тесты <1сек, integration <30сек
- ✅ Deterministic - никаких flaky tests
- ✅ Isolated - тесты не влияют друг на друга
Testing Levels¶
1. Unit Tests (Go Backend)¶
Location: backend/**/*_test.go
Scope: Individual functions, pure logic
Examples:
// backend/shared/validation/canonical/investment_validator_test.go
func TestValidateInvestmentAmount(t *testing.T) {
tests := []struct {
name string
amount SafeDecimal
currency string
wantErr bool
}{
{
name: "valid amount",
amount: NewSafeDecimal("100.00"),
currency: "USDC",
wantErr: false,
},
{
name: "below minimum",
amount: NewSafeDecimal("5.00"),
currency: "USDC",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ValidateInvestmentAmount(tt.amount, tt.currency, 1)
if tt.wantErr {
assert.True(t, result.HasErrors())
} else {
assert.False(t, result.HasErrors())
}
})
}
}
Run Unit Tests:
# All unit tests
make unit
# Specific package
go test ./backend/shared/validation/... -v
# With coverage
go test ./backend/... -cover
2. Integration Tests (Go Backend)¶
Location: backend/tests/integration/*_test.go
Scope: Multiple components, database, real services
Example:
// backend/tests/integration/balance_test.go
func TestBalanceCalculation_Integration(t *testing.T) {
// Setup
cfg := config.LoadTestConfig()
db := setupTestDatabase(t)
defer cleanupTestDatabase(t, db)
balanceService := balance_service.NewBalanceService(db, cfg)
// Create test user
userID := createTestUser(t, db, "user@saga-test.com")
// Create transactions
createTransaction(t, db, userID, "deposit", NewSafeDecimal("1000.00"))
createTransaction(t, db, userID, "investment", NewSafeDecimal("-500.00"))
// Test balance calculation
balance, err := balanceService.GetUserBalance(context.Background(), userID)
require.NoError(t, err)
assert.Equal(t, NewSafeDecimal("500.00"), balance)
}
Run Integration Tests:
# Core integration tests
make core
# All integration tests
cd backend && go test ./tests/integration/... -v
# Specific test
go test ./tests/integration -run TestBalanceCalculation -v
3. E2E Tests (Playwright)¶
Location: frontend/e2e/tests/**/*.spec.ts
Scope: Full user flows, browser automation, real backend
Example:
// frontend/e2e/tests/user/investment-flow.spec.ts
test('User can create investment end-to-end', async ({ page }) => {
// Setup: Create user with balance
const { token } = await createTestUserWithBalance('user@saga-test.com', '1000.00');
await page.setExtraHTTPHeaders({
'Authorization': `Bearer ${token}`
});
// Navigate to investments
await page.goto('http://app.saga.local:8080/investments');
// Select strategy
await page.click('text=Conservative (5%)');
// Enter amount
await page.fill('input[name="amount"]', '500');
// Submit investment
await page.click('button:has-text("Invest")');
// Verify success
await expect(page.locator('text=Investment Created')).toBeVisible();
// Verify balance updated
const balanceText = await page.locator('[data-testid="balance"]').innerText();
expect(balanceText).toContain('500.00');
});
Run E2E Tests:
# Smoke tests (быстрые critical paths)
make e2e-smoke
# Admin tests
make e2e-admin-quick
# Full E2E suite
make test-all
Test Organization¶
Test Pyramid Distribution:¶
/\
/ \ E2E (10%)
/____\ ~30 tests
/ \
/ \ Integration (30%)
/__________\ ~100 tests
/ \
/ \ Unit (60%)
/________________\~200 tests
Category Breakdown:¶
Unit Tests (~200):
- Validation logic: 50 tests
- SafeDecimal operations: 30 tests
- Business logic helpers: 40 tests
- Utility functions: 80 tests
Integration Tests (~100):
- API endpoints: 40 tests
- Service layer: 30 tests
- Repository layer: 20 tests
- Blockchain integration: 10 tests
E2E Tests (~30):
- Critical user flows: 15 tests
- Admin operations: 10 tests
- Error scenarios: 5 tests
Running Tests¶
Makefile Commands:¶
# Quick smoke test (30 seconds)
make smoke
# Unit tests (2-3 minutes)
make unit
# Core integration tests (3-5 minutes)
make core
# Foundation test suite (smoke + unit + core + e2e-smoke)
make test # ~5-7 minutes
# Full comprehensive testing (all groups)
make test-all # ~3 minutes (parallel)
# Specific E2E test
make e2e-single FILE=tests/user/investment-flow.spec.ts
Direct Commands:¶
# Go unit tests
go test ./backend/... -v -cover
# Go integration tests
cd backend && go test ./tests/integration/... -v
# Playwright E2E tests
cd frontend/e2e && npx playwright test
# Specific Playwright test
cd frontend/e2e && npx playwright test tests/user/auth.spec.ts
Test Data Management¶
Test Users:¶
Centralized in: backend/tests/integration/test_helpers.go
var TestUsers = map[string]TestUser{
"admin": {
Email: "admin@saga-test.com",
WalletAddress: "0x742d35Cc9Cf3C4C3a3F5d7B5f",
IsAdmin: true,
},
"user1": {
Email: "user1@saga-test.com",
WalletAddress: "0xE4B5F7D8C9A2B1C3D4E5F6G7H8",
IsAdmin: false,
},
}
func CreateTestUser(t *testing.T, email string) int64 {
user := TestUsers[email]
// Insert into database
// Return user ID
}
Test Database:¶
Setup & Teardown:
func setupTestDatabase(t *testing.T) *sql.DB {
cfg := config.LoadTestConfig()
db, err := sql.Open("postgres", cfg.GetDatabaseURL())
require.NoError(t, err)
// Run migrations
runMigrations(t, db)
return db
}
func cleanupTestDatabase(t *testing.T, db *sql.DB) {
// Truncate all tables
tables := []string{"users", "transactions", "investments", "withdrawals"}
for _, table := range tables {
_, err := db.Exec(fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table))
require.NoError(t, err)
}
}
Test Blockchain:¶
VPS Blockchain Integration:
func setupTestBlockchain(t *testing.T) *ethclient.Client {
cfg := config.LoadTestConfig()
client, err := ethclient.Dial(cfg.GetBlockchainRPCURL())
require.NoError(t, err)
// Verify connection
blockNumber, err := client.BlockNumber(context.Background())
require.NoError(t, err)
require.Greater(t, blockNumber, uint64(0))
return client
}
Test Utilities¶
Balance Helpers:¶
// backend/tests/integration/test_helpers.go
func CreateTestUserWithBalance(t *testing.T, email string, balance SafeDecimal) int64 {
userID := CreateTestUser(t, email)
// Create deposit transaction
tx := &models.Transaction{
UserID: userID,
Amount: balance,
Type: "deposit",
Currency: "USDC",
Status: "completed",
CreatedAt: time.Now().UTC(),
}
repo := repository.NewTransactionRepository(db)
err := repo.CreateTransaction(context.Background(), tx)
require.NoError(t, err)
return userID
}
JWT Helpers:¶
// frontend/e2e/utils/auth-helpers.ts
export async function generateTestJWT(email: string, isAdmin: boolean = false): Promise<string> {
const command = isAdmin
? `make jwt-admin-token EMAIL=${email}`
: `make jwt-user-token EMAIL=${email}`;
const { stdout } = await execAsync(command);
return stdout.trim();
}
API Helpers:¶
// frontend/e2e/utils/api-helpers.ts
export async function createTestInvestment(
token: string,
amount: string,
strategyID: number
): Promise<any> {
const response = await fetch('http://localhost:8080/api/user/investments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount,
strategy_id: strategyID
})
});
return response.json();
}
🐛 Troubleshooting Tests¶
Common Issues:¶
Issue: Database connection failed
# Check database is running
make status
# Verify database connection
PGPASSWORD=aisee psql -U aisee -h 127.0.0.1 -d saga -c "SELECT 1"
# Run migrations
make migration-up
Issue: Blockchain connection failed
# Check VPS blockchain node
make -f makefiles/development.mk vps-status
# Verify RPC endpoint
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://188.42.218.164:8545
Issue: E2E test timeout
# Increase timeout in playwright.config.ts
timeout: 60000 # 60 seconds
# Run with debug mode
cd frontend/e2e && npx playwright test --debug
# Run headed mode (see browser)
npx playwright test --headed
Issue: Flaky E2E tests
# Add explicit waits
await page.waitForSelector('[data-testid="balance"]', { timeout: 10000 });
# Wait for network idle
await page.waitForLoadState('networkidle');
# Retry mechanism
await expect(async () => {
const balance = await page.locator('[data-testid="balance"]').innerText();
expect(balance).toContain('1000');
}).toPass({ timeout: 30000 });
Test Coverage¶
Coverage Goals:¶
| Component | Target | Current |
|---|---|---|
| Backend Services | >80% | ~75% |
| Backend Repositories | >70% | ~65% |
| Backend Handlers | >60% | ~55% |
| Frontend Components | >50% | ~40% |
| E2E Critical Paths | 100% | ~90% |
Generate Coverage Reports:¶
# Go coverage
go test ./backend/... -coverprofile=coverage.out
go tool cover -html=coverage.out
# Frontend coverage (Jest)
cd frontend/user-app && npm run test:coverage
# E2E coverage
cd frontend/e2e && npx playwright test --reporter=html
Related Documentation¶
- JWT Testing - JWT-specific testing guide
📋 Метаданные¶
Версия: 2.4.82
Обновлено: 2025-10-21
Статус: Published