Testing Helpers & Examples
This directory provides comprehensive testing utilities for Zaparoo Core, enabling hardware-independent testing with zero dependencies on physical readers, databases, or filesystems.
Quick Reference
Mock Components
mocks.NewMockPlatform() - Mock platform for cross-platform testing
platform.SetupBasicMock() - Standard mock configuration
Reader Mocking
mocks.NewMockReader() - Mock card reader for testing token scanning
reader.On("ReadTokens", ...) - Set up token reading expectations
WebSocket Testing
helpers.NewWebSocketTestServer(t, handler) - Test WebSocket server
server.CreateWebSocketClient() - WebSocket test client
Database Testing
Mock Database Interfaces
helpers.NewMockUserDBI() - In-memory user database mock
helpers.NewMockMediaDBI() - In-memory media database mock
Database Fixtures & Helpers
fixtures.SampleTokens() - Pre-defined test token data
fixtures.SampleMedia() - Pre-defined media entries
fixtures.SamplePlaylists() - Pre-defined playlist data
helpers.HistoryEntryMatcher() - Flexible matching for history entries
Filesystem Testing
In-Memory Filesystem
helpers.NewMemoryFS() - In-memory filesystem using afero
fs.WriteFile(path, content, perm) - Create test files
fs.ReadFile(path) - Read test files
fs.FileExists(path) - Check if file exists
fs.CreateConfigFile(path, map) - Create JSON config from map
fs.CreateMediaDirectory(basePath) - Create sample media directory structure
fs.CreateDirectoryStructure(structure) - Create complex directory trees from map
Configuration Helpers
helpers.NewTestConfig(fs, configDir) - Test configuration with random port
helpers.NewTestConfigWithPort(fs, configDir, port) - Test configuration with specific port
Time-Based Testing
Clockwork for Deterministic Time
clockwork.NewFakeClock() - Fake clock for fast, deterministic tests
fakeClock.Advance(duration) - Progress time instantly without waiting
fakeClock.BlockUntilContext(ctx, n) - Wait for n goroutines to block on clock
clock.Now() - Get current time (fake or real)
clock.Since(t) - Calculate elapsed time
clock.NewTicker(d) - Create ticker (fake or real)
Production Code Pattern
type MyService struct {
clock clockwork.Clock // Inject clock for testability
}
func NewMyService() *MyService {
return &MyService{
clock: clockwork.NewRealClock(), // Real clock in production
}
}
Test Pattern
func TestMyService(t *testing.T) {
fakeClock := clockwork.NewFakeClock()
service := &MyService{clock: fakeClock} // Inject fake clock
// Advance time instantly
fakeClock.Advance(1 * time.Minute)
// No time.Sleep() needed!
}
See TESTING.md for comprehensive examples and best practices.
Fuzz Testing
Native Go Fuzzing (Go 1.18+)
func FuzzFunctionName(f *testing.F) - Fuzz test function signature
f.Add(inputs...) - Seed corpus with known test cases
f.Fuzz(func(t *testing.T, inputs...) { ... }) - Fuzz target function
Running Fuzz Tests
# Run all tests (includes fuzz corpus) - FAST
task test
# Manual fuzzing (runs until failure or Ctrl+C)
go test -fuzz=FuzzParseVirtualPathStr ./pkg/helpers/virtualpath/
# Time-boxed fuzzing
go test -fuzz=FuzzName -fuzztime=30s ./pkg/helpers/
Example Fuzz Test
func FuzzParseURI(f *testing.F) {
// Seed corpus
f.Add("steam://123/Game")
f.Add("") // Edge case
f.Fuzz(func(t *testing.T, uri string) {
result, err := ParseURI(uri)
// Test properties (invariants)
if err == nil && !utf8.ValidString(result) {
t.Errorf("Invalid UTF-8: %q", result)
}
})
}
Example fuzz tests: pkg/helpers/uris_fuzz_test.go, pkg/helpers/paths_fuzz_test.go
API Testing
WebSocket & JSON-RPC
helpers.NewWebSocketTestServer(t, messageHandler) - WebSocket server for testing
client.SendMessage(data) - Send test messages
client.ReceiveMessage() - Receive and validate responses
HTTP Testing
- Standard Go
net/http/httptest integration
- Context-aware HTTP client helpers
Complete Examples
The examples/ directory contains working examples demonstrating all testing patterns:
Core Testing Patterns
api_example_test.go - WebSocket communication, JSON-RPC testing, HTTP endpoints
database_example_test.go - Database operations, mock expectations, transaction testing
filesystem_example_test.go - File operations, configuration management, in-memory filesystem
mock_usage_example_test.go - Platform mocking, reader mocking, expectation setup
Service Layer Testing
service_token_processing_test.go - End-to-end token processing workflows
service_state_management_test.go - Application state management testing
service_zapscript_test.go - ZapScript execution and custom launcher testing
Common Patterns
Basic Test Setup
func TestYourFeature(t *testing.T) {
t.Parallel()
// Setup mocks
platform := mocks.NewMockPlatform()
platform.SetupBasicMock()
// Setup database
db := &database.Database{
UserDB: helpers.NewMockUserDBI(),
MediaDB: helpers.NewMockMediaDBI(),
}
// Setup state
st, notifications := state.NewState(platform)
defer st.StopService()
// Your test logic here
}
API Server Testing
func TestAPIEndpoint(t *testing.T) {
// Setup test environment
platform := mocks.NewMockPlatform()
platform.SetupBasicMock()
fs := helpers.NewMemoryFS()
cfg, err := helpers.NewTestConfigWithPort(fs, t.TempDir(), 0)
require.NoError(t, err)
// Start server
go api.Start(platform, cfg, st, tokenQueue, db, notifications)
// Test API calls...
}
Token Processing Testing
func TestTokenProcessing(t *testing.T) {
// Setup mock reader
reader := mocks.NewMockReader()
reader.On("ReadTokens", mock.Anything).Return([]tokens.Token{
{UID: "test-uid", Data: "test-data"},
}, nil)
// Test processing...
}
Database Operation Testing
func TestDatabaseOperations(t *testing.T) {
userDB := helpers.NewMockUserDBI()
// Set expectations
userDB.On("AddHistory", helpers.HistoryEntryMatcher()).Return(nil)
// Test your function
err := YourFunction(userDB)
require.NoError(t, err)
// Verify expectations
userDB.AssertExpectations(t)
}
Test Data & Fixtures
The fixtures/ directory provides pre-built test data:
Token Fixtures
fixtures.SampleTokens() - Slice of common test tokens
fixtures.NewNFCToken(), NewMifareToken(), NewAmiiboToken() - Individual token types
fixtures.NewTokenCollection() - Collection with lookup methods
fixtures.SampleMedia() - Slice of common test media
fixtures.NewRetroGame(), NewModernGame(), NewArcadeGame() - Individual media types
fixtures.NewMediaCollection() - Collection with lookup methods
Other Fixtures
fixtures.SamplePlaylists() - Pre-defined playlist data
fixtures.HistoryEntries - Pre-populated history entries
Best Practices
- Always use
t.Parallel() for independent tests
- Use unique ports for concurrent API server tests (8000 + testID pattern)
- Clean up resources with
defer statements
- Use meaningful test data from fixtures rather than hardcoded values
- Test behavior, not implementation - focus on what the code does, not how
- Leverage mocks for hardware dependencies - no real readers or databases needed