Testing with Mock Providers¶
The tts/providertest package provides mock TTS providers and audio fixtures for testing your integrations without requiring API keys or network access.
Quick Start¶
import "github.com/plexusone/omnivoice-core/tts/providertest"
func TestMyFeature(t *testing.T) {
// Create a mock provider
mock := providertest.NewMockProvider()
// Use it like a real provider
result, err := mock.Synthesize(ctx, "Hello world", tts.SynthesisConfig{})
if err != nil {
t.Fatal(err)
}
// Result contains valid audio data
t.Logf("Generated %d bytes of %s audio", len(result.Audio), result.Format)
}
Provider-Specific Mocks¶
Pre-configured mocks that simulate specific provider behavior with realistic voice lists:
ElevenLabs Mock¶
mock := providertest.NewElevenLabsMock()
// Name() returns "elevenlabs"
// ListVoices() returns Rachel, Bella, Antoni
voices, _ := mock.ListVoices(ctx)
for _, v := range voices {
fmt.Printf("%s: %s (%s)\n", v.ID, v.Name, v.Gender)
}
// Output:
// 21m00Tcm4TlvDq8ikWAM: Rachel (female)
// EXAVITQu4vr4xnSDxMaL: Bella (female)
// ErXwobaYiN019PkySvjV: Antoni (male)
Deepgram Mock¶
mock := providertest.NewDeepgramMock()
// Name() returns "deepgram"
// ListVoices() returns Asteria, Luna, Orion
OpenAI Mock¶
mock := providertest.NewOpenAIMock()
// Name() returns "openai"
// ListVoices() returns all 6 OpenAI TTS voices:
// Alloy, Echo, Fable, Onyx, Nova, Shimmer
Configurable Mock Behaviors¶
Use MockProviderOption functions to customize mock behavior:
Fixed Duration Audio¶
// Always return 2-second audio regardless of text length
mock := providertest.NewMockProviderWithOptions(
providertest.WithFixedDuration(2000), // milliseconds
)
Realistic Timing¶
// Duration proportional to text length (~50ms per character)
mock := providertest.NewMockProviderWithOptions(
providertest.WithRealisticTiming(),
)
// Short text = short audio
r1, _ := mock.Synthesize(ctx, "Hi", config) // ~100ms (minimum)
// Long text = longer audio
r2, _ := mock.Synthesize(ctx, "Hello world!", config) // ~600ms
Simulating Latency¶
// Add 100ms delay to each call
mock := providertest.NewMockProviderWithOptions(
providertest.WithLatency(100 * time.Millisecond),
)
Error Injection¶
// Always return an error
mock := providertest.NewMockProviderWithOptions(
providertest.WithError(providertest.ErrMockRateLimit),
)
_, err := mock.Synthesize(ctx, "test", config)
// err == ErrMockRateLimit
Available error types:
| Error | Description |
|---|---|
ErrMockRateLimit |
Simulates rate limiting |
ErrMockQuotaExceeded |
Simulates quota exhaustion |
ErrMockNetworkError |
Simulates network failures |
ErrMockInvalidVoice |
Simulates invalid voice ID |
Fail After N Calls¶
// Succeed 3 times, then fail (for testing retry/failover logic)
mock := providertest.NewMockProviderWithOptions(
providertest.WithFailAfterN(3, providertest.ErrMockQuotaExceeded),
)
mock.Synthesize(ctx, "1", config) // Success
mock.Synthesize(ctx, "2", config) // Success
mock.Synthesize(ctx, "3", config) // Success
mock.Synthesize(ctx, "4", config) // Error: quota exceeded
Combining Options¶
mock := providertest.NewMockProviderWithOptions(
providertest.WithName("my-provider"),
providertest.WithRealisticTiming(),
providertest.WithLatency(50 * time.Millisecond),
)
WAV Audio Fixtures¶
Generate valid WAV files for testing audio processing:
Basic Usage¶
// Generate 1-second WAV at 22050 Hz
fixture := providertest.GenerateWAVFixture(1000, 22050)
fmt.Printf("Format: %s\n", fixture.Format) // "wav"
fmt.Printf("Duration: %dms\n", fixture.DurationMs) // 1000
fmt.Printf("Sample Rate: %d\n", fixture.SampleRate) // 22050
fmt.Printf("Size: %d bytes\n", len(fixture.Data)) // 44144
Convenience Functions¶
// 100ms WAV at 22050 Hz (quick tests)
short := providertest.GenerateShortWAV()
// 1 second WAV at 22050 Hz
oneSecond := providertest.GenerateOneSecondWAV()
WAV Format Details¶
Generated WAV files are valid PCM audio:
- Format: PCM (uncompressed)
- Channels: 1 (mono)
- Bit Depth: 16-bit
- Content: Silence (zeros)
- Header: Full 44-byte RIFF/WAV header
The audio can be parsed by ffprobe, ffmpeg, and other audio tools.
Verifying WAV Headers¶
fixture := providertest.GenerateWAVFixture(1000, 22050)
// Check RIFF header
if string(fixture.Data[0:4]) != "RIFF" {
t.Error("Invalid WAV: missing RIFF header")
}
// Check WAVE format
if string(fixture.Data[8:12]) != "WAVE" {
t.Error("Invalid WAV: missing WAVE format")
}
Common Test Patterns¶
Testing TTS Integration¶
func TestTTSClient(t *testing.T) {
mock := providertest.NewElevenLabsMock()
client := mytts.NewClient(mock)
audio, err := client.TextToSpeech(ctx, "Hello")
if err != nil {
t.Fatalf("TextToSpeech failed: %v", err)
}
if len(audio) == 0 {
t.Error("Expected audio data")
}
}
Testing Error Handling¶
func TestRateLimitHandling(t *testing.T) {
mock := providertest.NewMockProviderWithOptions(
providertest.WithError(providertest.ErrMockRateLimit),
)
client := mytts.NewClient(mock)
_, err := client.TextToSpeech(ctx, "test")
if !errors.Is(err, providertest.ErrMockRateLimit) {
t.Errorf("Expected rate limit error, got: %v", err)
}
}
Testing Retry Logic¶
func TestRetryOnFailure(t *testing.T) {
callCount := 0
mock := providertest.NewMockProviderWithOptions(
providertest.WithFailAfterN(2, providertest.ErrMockNetworkError),
)
client := mytts.NewClientWithRetry(mock, 3) // 3 retries
// First call uses the mock's first 2 successful attempts,
// then fails and retries should kick in
_, err := client.TextToSpeech(ctx, "test")
// Verify retry behavior based on your implementation
}
Testing Provider Failover¶
func TestFailover(t *testing.T) {
// Primary fails immediately
primary := providertest.NewMockProviderWithOptions(
providertest.WithError(providertest.ErrMockQuotaExceeded),
)
// Fallback succeeds
fallback := providertest.NewDeepgramMock()
client := mytts.NewFailoverClient(primary, fallback)
result, err := client.TextToSpeech(ctx, "Hello")
if err != nil {
t.Fatalf("Failover should have succeeded: %v", err)
}
// Verify fallback was used
if result.Provider != "deepgram" {
t.Errorf("Expected deepgram fallback, got %s", result.Provider)
}
}
Testing Context Cancellation¶
func TestContextCancellation(t *testing.T) {
mock := providertest.NewMockProviderWithOptions(
providertest.WithLatency(5 * time.Second),
)
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
start := time.Now()
_, err := mock.Synthesize(ctx, "test", tts.SynthesisConfig{})
elapsed := time.Since(start)
if err == nil {
t.Error("Expected context cancellation error")
}
if elapsed > 500*time.Millisecond {
t.Errorf("Should have cancelled quickly, took %v", elapsed)
}
}
Testing with Conformance Suite¶
func TestMyMock_Conformance(t *testing.T) {
mock := providertest.NewElevenLabsMock()
providertest.RunInterfaceTests(t, providertest.Config{
Provider: mock,
SkipIntegration: true,
})
}
Best Practices¶
-
Use provider-specific mocks when testing provider-dependent behavior (voice lists, naming conventions)
-
Use configurable mocks when testing error handling, retry logic, or timing-sensitive code
-
Generate fresh fixtures for each test to avoid shared state issues
-
Test context cancellation to ensure your code properly handles timeouts
-
Combine with conformance tests to verify your integration works with any provider
See Also¶
- Provider Conformance Testing - TRD for conformance test design
- v0.7.0 Release Notes - Full list of mock provider features