Testing Guide
Fluxbase has comprehensive testing infrastructure covering unit tests, integration tests, end-to-end tests, and performance tests for both backend (Go) and SDK (TypeScript).
Overview
Section titled “Overview”┌─────────────────────────────────────────────────────────────┐│ Testing Pyramid ││ ││ ┌─────────────┐ ││ │ E2E Tests │ (Slowest, Full Stack) ││ └─────────────┘ ││ ┌───────────────────────┐ ││ │ Integration Tests │ (Database + API) ││ └───────────────────────┘ ││ ┌──────────────────────────────────┐ ││ │ Unit Tests │ (Fastest) ││ └──────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘Backend Testing (Go)
Section titled “Backend Testing (Go)”Fluxbase backend uses Go’s built-in testing framework with testify for assertions and test suites.
Test Types
Section titled “Test Types”1. Unit Tests
Section titled “1. Unit Tests”Test individual functions in isolation. Location: internal/*/
make test-fast # Run all unit testsgo test -v ./internal/auth/... # Test specific packagego test -v -run TestPasswordHasher ... # Run specific testExample:
func TestPasswordHasher(t *testing.T) { hasher := auth.NewPasswordHasher() hash, err := hasher.HashPassword("password123") assert.NoError(t, err) assert.NoError(t, hasher.VerifyPassword(hash, "password123")) assert.Error(t, hasher.VerifyPassword(hash, "wrong"))}2. Integration Tests
Section titled “2. Integration Tests”Test components with real database. Location: internal/api/*_integration_test.go
go test -v ./internal/api/... -run Integration # Run all integration testsExample:
func (s *Suite) TestUploadFile() { resp := s.tc.NewRequest("POST", "/api/v1/storage/buckets/test/files"). WithFile("file", "test.txt", []byte("Hello")).Send() resp.AssertStatus(201)}3. End-to-End (E2E) Tests
Section titled “3. End-to-End (E2E) Tests”Test full application stack. Location: test/e2e/
make test-e2e # Run all E2E testsmake test-full # Run unit + integration + E2EExample:
func (s *Suite) TestSignUpAndSignIn() { resp := s.tc.NewRequest("POST", "/api/v1/auth/signup"). WithBody(map[string]interface{}{"email": "test@example.com", "password": "pass"}). Send() resp.AssertStatus(201) s.Contains(resp.JSON(), "access_token")}4. Performance Tests
Section titled “4. Performance Tests”Test system performance under load. Location: test/performance/
go test -bench=. -benchmem ./test/performance/... # Run benchmarksTest Helpers
Section titled “Test Helpers”TestContext provides test dependencies:
tc := test.NewTestContext(t)defer tc.Close()// tc.DB, tc.Server, tc.App, tc.ConfigHTTP requests:
resp := tc.NewRequest("POST", "/api/v1/tables/users"). WithBody(map[string]interface{}{"name": "John"}). WithAuth("Bearer token"). Send()resp.AssertStatus(201)Database:
tc.ExecuteSQL("INSERT INTO users...")tc.CleanDatabase()SDK Testing (TypeScript)
Section titled “SDK Testing (TypeScript)”The TypeScript SDK uses Vitest for testing with mocking and assertions.
Running SDK Tests
Section titled “Running SDK Tests”cd sdk
# Run all testsnpm test
# Run tests in watch modenpm test -- --watch
# Run specific test filenpm test -- src/auth.test.ts
# Run with UInpm run test:ui
# Type checkingnpm run type-checkTest Structure
Section titled “Test Structure”Location: sdk/src/*.test.ts
Available Test Files:
src/auth.test.ts- Authentication testssrc/admin.test.ts- Admin SDK testssrc/management.test.ts- Management SDK testssrc/query-builder.test.ts- Query builder testssrc/realtime.test.ts- Realtime testssrc/storage.test.ts- Storage testssrc/aggregations.test.ts- Aggregation tests
Example SDK Test
Section titled “Example SDK Test”Authentication Test (from sdk/src/auth.test.ts):
import { describe, it, expect, beforeEach, vi } from "vitest";import { FluxbaseAuth } from "./auth";import type { FluxbaseFetch } from "./fetch";
describe("FluxbaseAuth", () => { let auth: FluxbaseAuth; let mockFetch: FluxbaseFetch;
beforeEach(() => { // Create mock fetch mockFetch = { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn(), } as unknown as FluxbaseFetch;
auth = new FluxbaseAuth(mockFetch); });
describe("signUp", () => { it("should sign up a new user", async () => { const mockResponse = { user: { id: "user-123", email: "test@example.com", }, access_token: "token-123", refresh_token: "refresh-123", };
vi.mocked(mockFetch.post).mockResolvedValue(mockResponse);
const result = await auth.signUp({ email: "test@example.com", password: "password123", });
expect(mockFetch.post).toHaveBeenCalledWith("/api/v1/auth/signup", { email: "test@example.com", password: "password123", });
expect(result).toEqual(mockResponse); expect(result.user.email).toBe("test@example.com"); expect(result.access_token).toBe("token-123"); });
it("should handle sign up errors", async () => { vi.mocked(mockFetch.post).mockRejectedValue( new Error("Email already exists") );
await expect( auth.signUp({ email: "test@example.com", password: "password123", }) ).rejects.toThrow("Email already exists"); }); });
describe("signIn", () => { it("should sign in existing user", async () => { const mockResponse = { user: { id: "user-123", email: "test@example.com" }, access_token: "token-123", refresh_token: "refresh-123", };
vi.mocked(mockFetch.post).mockResolvedValue(mockResponse);
const result = await auth.signIn({ email: "test@example.com", password: "password123", });
expect(mockFetch.post).toHaveBeenCalledWith("/api/v1/auth/signin", { email: "test@example.com", password: "password123", });
expect(result.access_token).toBe("token-123"); }); });
describe("signOut", () => { it("should sign out user", async () => { vi.mocked(mockFetch.post).mockResolvedValue({});
await auth.signOut();
expect(mockFetch.post).toHaveBeenCalledWith("/api/v1/auth/signout", {}); }); });});Mocking with Vitest
Section titled “Mocking with Vitest”Mock HTTP Fetch:
import { vi } from "vitest";
const mockFetch = { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn(),};
// Set mock return valuevi.mocked(mockFetch.post).mockResolvedValue({ data: { id: 123, name: "Test" },});
// Verify mock was calledexpect(mockFetch.post).toHaveBeenCalledWith("/api/endpoint", { data: "test" });
// Check call countexpect(mockFetch.post).toHaveBeenCalledTimes(1);Mock Modules:
import { vi } from "vitest";
vi.mock("./storage", () => ({ FluxbaseStorage: vi.fn(() => ({ upload: vi.fn().mockResolvedValue({ url: "https://example.com/file.jpg" }), download: vi.fn().mockResolvedValue(new Blob()), })),}));Test Configuration
Section titled “Test Configuration”Backend Test Configuration
Section titled “Backend Test Configuration”Tests use a separate fluxbase_test database:
Environment Variables:
# Test databaseFLUXBASE_DATABASE_HOST=postgresFLUXBASE_DATABASE_USER=fluxbase_appFLUXBASE_DATABASE_PASSWORD=fluxbase_app_passwordFLUXBASE_DATABASE_DATABASE=fluxbase_test
# Test servicesFLUXBASE_EMAIL_SMTP_HOST=mailhogFLUXBASE_STORAGE_S3_ENDPOINT=minio:9000Test Config (from test/e2e_helpers.go):
func GetTestConfig() *config.Config { return &config.Config{ Server: config.ServerConfig{ Address: ":8081", ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, BodyLimit: 10 * 1024 * 1024, }, Database: config.DatabaseConfig{ Host: "postgres", Port: 5432, User: "fluxbase_app", Password: "fluxbase_app_password", Database: "fluxbase_test", SSLMode: "disable", MaxConnections: 25, MinConnections: 5, }, Auth: config.AuthConfig{ JWTSecret: "test-secret-key-for-testing-only", JWTExpiry: 15 * time.Minute, RefreshExpiry: 168 * time.Hour, EnableSignup: true, }, Debug: true, }}SDK Test Configuration
Section titled “SDK Test Configuration”SDK tests use mocked HTTP calls and don’t require a running server.
Vitest Configuration (sdk/vitest.config.ts):
import { defineConfig } from "vitest/config";
export default defineConfig({ test: { globals: true, environment: "node", coverage: { provider: "v8", reporter: ["text", "json", "html"], exclude: ["node_modules/", "dist/"], }, },});CI/CD Testing
Section titled “CI/CD Testing”Tests run automatically in GitHub Actions on every push and pull request.
GitHub Actions Workflow (.github/workflows/test.yml):
name: Tests
on: push: branches: [main, develop] pull_request: branches: [main, develop]
jobs: test-backend: runs-on: ubuntu-latest
services: postgres: image: postgis/postgis:18-3.6 env: POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432
steps: - uses: actions/checkout@v5
- name: Set up Go uses: actions/setup-go@v5 with: go-version: "1.22"
- name: Setup test database run: ./test/scripts/setup_test_db.sh
- name: Run tests run: make test-full
- name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage.out
test-sdk: runs-on: ubuntu-latest
steps: - uses: actions/checkout@v5
- name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "20"
- name: Install dependencies working-directory: ./sdk run: npm install
- name: Run SDK tests working-directory: ./sdk run: npm test
- name: Type check working-directory: ./sdk run: npm run type-checkTest Coverage
Section titled “Test Coverage”Backend Coverage
Section titled “Backend Coverage”Generate test coverage report:
# Generate coverage reportgo test -cover -coverprofile=coverage.out ./...
# View coverage in terminalgo tool cover -func=coverage.out
# Generate HTML reportgo tool cover -html=coverage.out -o coverage.html
# Open in browseropen coverage.html # macOSxdg-open coverage.html # LinuxCoverage Targets:
- Overall: > 70%
- Critical paths (auth, RLS, security): > 90%
- Utilities: > 80%
SDK Coverage
Section titled “SDK Coverage”cd sdk
# Run tests with coveragenpm test -- --coverage
# View coverage reportopen coverage/index.htmlCoverage Targets:
- Overall: > 80%
- Core modules: > 90%
Best Practices
Section titled “Best Practices”| Practice | Description |
|---|---|
| Use test suites | Organize related tests with testify/suite for setup/teardown |
| Test isolation | Clean data between tests (TRUNCATE TABLE, vi.clearAllMocks()) |
| Descriptive names | TestCreateUserWithValidEmail not TestUser |
| Test error cases | Test happy path AND error conditions |
| Parallel tests | Use t.Parallel() for independent unit tests |
| Skip slow tests | if testing.Short() { t.Skip() } then run go test -short |
| Table-driven tests | Test multiple scenarios efficiently with test tables |
Example table-driven test:
func TestValidateEmail(t *testing.T) { tests := []struct { email string wantErr bool }{ {"user@example.com", false}, {"invalid", true}, } for _, tt := range tests { err := ValidateEmail(tt.email) if (err != nil) != tt.wantErr { t.Errorf("error = %v, wantErr %v", err, tt.wantErr) } }}8. Mock External Services
Section titled “8. Mock External Services”Always mock external services in tests:
// Mock HTTP clientvi.mock("./http-client", () => ({ httpClient: { post: vi.fn().mockResolvedValue({ data: "mocked" }), },}));
// Mock WebSocketvi.mock("./websocket", () => ({ WebSocketClient: vi.fn(() => ({ connect: vi.fn(), send: vi.fn(), close: vi.fn(), })),}));Debugging Tests
Section titled “Debugging Tests”Backend Debugging
Section titled “Backend Debugging”Run Single Test:
go test -v -run TestSpecificTest ./test/e2e/...Run with Race Detector:
go test -race ./...Enable Debug Logging:
FLUXBASE_DEBUG=true go test -v ./test/e2e/...Use Debugger (Delve):
# Install delvego install github.com/go-delve/delve/cmd/dlv@latest
# Debug specific testdlv test ./test/e2e -- -test.run TestAuthSuiteSDK Debugging
Section titled “SDK Debugging”Run with Verbose Output:
npm test -- --reporter=verboseDebug in VS Code:
Add to .vscode/launch.json:
{ "type": "node", "request": "launch", "name": "Debug Vitest Tests", "runtimeExecutable": "npm", "runtimeArgs": ["test", "--", "--run"], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen"}Watch Mode:
npm test -- --watchTroubleshooting
Section titled “Troubleshooting”| Issue | Solution |
|---|---|
| Database connection | pg_isready -h postgres -U postgres, restart database |
| Port conflicts | lsof -i :8080, kill -9 <PID> |
| Race conditions | Run go test -race ./..., fix shared state access |
| Flaky tests | Use deterministic time, proper mocking, ensure cleanup |
Performance Testing
Section titled “Performance Testing”Go Benchmarks
Section titled “Go Benchmarks”func BenchmarkPasswordHashing(b *testing.B) { hasher := auth.NewPasswordHasher() password := "MySecurePassword123!"
b.ResetTimer()
for i := 0; i < b.N; i++ { _, _ = hasher.HashPassword(password) }}
func BenchmarkQueryExecution(b *testing.B) { tc := test.NewTestContext(&testing.T{}) defer tc.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ { _ = tc.QuerySQL("SELECT * FROM users LIMIT 100") }}Run Benchmarks:
# Run all benchmarksgo test -bench=. ./...
# Run specific benchmarkgo test -bench=BenchmarkPasswordHashing ./internal/auth/...
# With memory profilinggo test -bench=. -benchmem ./...
# CPU profilinggo test -bench=. -cpuprofile=cpu.prof ./...go tool pprof cpu.profSummary
Section titled “Summary”Fluxbase provides comprehensive testing infrastructure:
- ✅ Backend Testing: Unit, integration, E2E, performance tests with Go
- ✅ SDK Testing: Vitest-based TypeScript testing with mocking
- ✅ Test Helpers: TestContext, HTTP builders, database utilities
- ✅ CI/CD Integration: Automated tests on every push
- ✅ Coverage Reporting: Track test coverage over time
- ✅ Performance Testing: Benchmarks and load tests
- ✅ Debugging Tools: Race detector, verbose logging, debugger support
Write tests early, test all paths (happy and error), maintain high coverage, and run tests frequently to ensure code quality and prevent regressions.