Unit
A zero-dependency library for testing Ergo Framework actors with fluent API
The Ergo Unit Testing Library makes testing actor-based systems simple and reliable. It provides specialized tools designed specifically for the unique challenges of testing actors, with zero external dependencies and an intuitive, readable API.
What You'll Learn
This guide takes you from simple actor tests to complex distributed scenarios. Here's the journey:
Getting Started (You Are Here!)
Your First Test - Simple echo and counter examples
Built-in Assertions - Simple tools for common checks
Basic Message Testing - Verify actors send the right messages
Basic Logging Testing - Verify your actors provide good observability
Intermediate Skills (Next Steps)
Configuration Testing - Test environment-driven behavior
Complex Message Patterns - Handle sophisticated message flows
Basic Process Spawning - Test actor creation and lifecycle
Event Inspection - Debug and analyze actor behavior
Advanced Features (When You Need Them)
Actor Termination - Test error handling and graceful shutdowns
Exit Signals - Manage process lifecycles in supervision trees
Scheduled Operations - Test cron jobs and time-based behavior
Network & Distribution - Test multi-node actor systems
Expert Level (Complex Scenarios)
Dynamic Value Capture - Handle generated IDs, timestamps, and random data
Complex Workflows - Test multi-step business processes
Performance & Load Testing - Verify behavior under stress
Tip: The documentation follows this learning path. You can jump to advanced topics if needed, but starting from the beginning ensures you understand the foundations.
Why Testing Actors is Different
Traditional testing tools don't work well with actors. Here's why:
The Challenge: Actors Are Not Functions
Regular code testing follows a simple pattern:
But actors are fundamentally different:
They run asynchronously - you send a message and the response comes later
They maintain state - previous messages affect future behavior
They spawn other actors - creating complex hierarchies
They communicate only via messages - no direct access to internal state
They can fail and restart - requiring lifecycle testing
What Makes Actor Testing Hard
Asynchronous Communication
Message Flow Complexity
Dynamic Process Creation
State Changes Over Time
How This Library Solves Actor Testing
The Ergo Unit Testing Library addresses each of these challenges:
Event Capture - See Everything Your Actor Does
Instead of guessing what happened, the library automatically captures every actor operation:
Fluent Assertions - Test What Matters
Express your test intentions clearly:
Dynamic Value Handling - Work With Generated Data
Capture and reuse dynamically generated values:
State Testing Through Behavior - Verify State Changes
Test state indirectly by verifying behavioral changes:
Why Zero Dependencies Matters
Actor testing is complex enough without dependency management headaches:
No version conflicts - Works with any Go testing setup
No external tools - Everything needed is built-in
Simple imports - Just
import "ergo.services/ergo/testing/unit"Fast execution - No overhead from external libraries
Core Concepts
Now that you understand why actor testing is different, let's explore the key concepts that make this library work.
The Event-Driven Testing Model
Everything your actor does becomes a testable "event".
When you run this simple test:
Here's what happens behind the scenes:
Your actor receives the message - Normal actor behavior
Your actor sends a response - Normal actor behavior
The library captures a
SendEvent- Testing magicYou verify the captured event - Your assertion
The library automatically captures these events:
SendEvent- When your actor sends a messageSpawnEvent- When your actor creates child processesLogEvent- When your actor writes log messagesTerminateEvent- When your actor shuts down
Why Events Matter
Events solve the fundamental challenge of testing asynchronous systems:
Instead of this (impossible):
You do this (works perfectly):
The Fluent Assertion API
The library provides a readable, chainable API that expresses test intentions clearly:
Benefits of the fluent API:
Readable - Tests read like English sentences
Discoverable - IDE autocomplete guides you through options
Flexible - Chain only the validations you need
Precise - Specify exactly what matters for each test
Installation
Your First Actor Test
Let's start with the simplest possible actor test to understand the basics:
A Simple Echo Actor
Testing the Echo Actor
What Just Happened?
This simple test demonstrates the core pattern:
unit.Spawn()- Creates a test actor in an isolated environmentactor.SendMessage()- Sends a message to your actor (like prod would)actor.ShouldSend()- Verifies that your actor sent the expected message
Key insight: You're not testing internal state - you're testing behavior. You verify what the actor does (sends messages) rather than what it contains (internal variables).
Why This Works
The testing library automatically captures everything your actor does:
Every message sent by your actor
Every process spawned by your actor
Every log message written by your actor
When your actor terminates
Then it provides fluent assertions to verify these captured events.
Adding Slightly More Complexity
Let's test an actor that maintains some state:
This shows how you test stateful behavior without accessing internal state - by observing how the actor's responses change over time.
Built-in Assertions
Before diving into complex actor testing, let's cover the simple assertion utilities you'll use throughout your tests.
Why Built-in Assertions Matter for Actor Testing:
Actor tests often need to verify simple conditions alongside complex event assertions. Rather than forcing you to import external testing libraries (which could conflict with your project dependencies), the unit testing library provides everything you need:
Available Assertions
Equality Testing:
Boolean Testing:
Nil Testing:
String Testing:
Type Testing:
Why Zero Dependencies Matter
No Import Conflicts:
Consistent Error Messages: All assertions provide clear, consistent error messages that integrate well with the actor testing output.
Framework Agnostic: Works with any Go testing setup - standard go test, IDE test runners, CI/CD systems, etc.
Basic Message Testing
Now that you understand the fundamentals, let's explore message testing in more depth.
What Comes Next
Now you'll learn how to test different aspects of actor behavior, building from simple to complex:
Fundamentals (You're here!)
Basic message sending and receiving
Simple process creation
Logging and observability
Configuration testing
Intermediate Skills
Complex message patterns
Event inspection and debugging
Actor lifecycle and termination
Error handling and recovery
Advanced Features
Scheduled operations (cron jobs)
Network and distribution
Performance and load testing
Basic Logging Testing
Logging is crucial for production actors - it provides visibility into what your actors are doing and helps with debugging. Let's learn how to test logging behavior.
Why Test Logging?
Logging tests ensure:
Your actors provide sufficient information for monitoring
Debug information is available when needed
Log levels are respected (don't log debug in production)
Sensitive operations are properly audited
Simple Logging Test
Testing Different Log Levels
Testing Log Content
Logging Best Practices for Testing
Structure your log messages to make them easy to test:
Test log levels appropriately:
Error- Test that errors are logged when they occurWarning- Test that concerning but non-fatal events are capturedInfo- Test that important business events are recordedDebug- Test that detailed troubleshooting info is available
Intermediate Skills
Now that you've mastered the basics, let's tackle more complex testing scenarios.
Configuration and Environment Testing
Real actors often behave differently based on configuration. Let's test this:
The Spawn function creates an isolated testing environment for your actor. Unlike production actors that run in a complex node environment, test actors run in a controlled sandbox where every operation is captured for verification.
Key Benefits:
Isolation: Each test actor runs independently without affecting other tests
Deterministic: Test outcomes are predictable and repeatable
Observable: All actor operations are automatically captured as events
Configurable: Fine-tune the testing environment to match your needs
Example Actor:
Test Implementation:
Configuration Options - Fine-Tuning the Test Environment
Test configuration allows you to simulate different runtime conditions without requiring complex setup:
Environment Variables (WithEnv): Test how your actors behave with different configurations without changing production code. Useful for testing feature flags, database URLs, timeout values, and other configuration-driven behavior.
Log Levels (WithLogLevel): Control the verbosity of test output and verify that your actors log appropriately at different levels. Critical for testing monitoring and debugging capabilities.
Process Hierarchy (WithParent, WithRegister): Test actors that need to interact with parent processes or require specific naming for registration-based lookups.
Message Testing
ShouldSend() - Verifying Actor Communication
ShouldSend() - Verifying Actor CommunicationMessage testing is the heart of actor validation. Since actors communicate exclusively through messages, verifying message flow is crucial for ensuring correct behavior.
Why Message Testing Matters:
Validates Integration: Ensures actors communicate correctly with their dependencies
Confirms Business Logic: Verifies that the right messages are sent in response to inputs
Detects Side Effects: Catches unintended message sends that could cause bugs
Tests Message Content: Validates that message payloads contain correct data
Example Actor:
Test Implementation:
Advanced Message Matching - Flexible Validation Patterns
When testing complex message structures or dynamic content, the library provides powerful matching capabilities:
Pattern Matching Benefits:
Partial Validation: Test only the fields that matter for your specific test case
Dynamic Content Handling: Validate messages with timestamps, UUIDs, or generated IDs
Type Safety: Ensure messages are of the correct type even when content varies
Negative Testing: Verify that certain messages are NOT sent in specific scenarios
Process Spawning
ShouldSpawn() - Testing Process Lifecycle Management
ShouldSpawn() - Testing Process Lifecycle ManagementProcess spawning is a fundamental actor pattern for building hierarchical systems. The testing library provides comprehensive tools for verifying that actors create, configure, and manage child processes correctly.
Why Process Spawning Tests Matter:
Resource Management: Ensure actors don't spawn too many or too few processes
Configuration Propagation: Verify that child processes receive correct configuration
Error Handling: Test behavior when process spawning fails
Supervision Trees: Validate that supervisors manage their children appropriately
Example Actor:
Test Implementation:
Dynamic Process Testing - Handling Generated Values
Real-world actors often generate dynamic values like session IDs, request tokens, or timestamps. The library provides sophisticated tools for capturing and validating these dynamic values.
Dynamic Value Testing Scenarios:
Session Management: Test actors that create sessions with generated IDs
Request Tracking: Verify that request tokens are properly generated and used
Time-based Operations: Validate actors that schedule work or create timestamps
Resource Allocation: Test dynamic assignment of resources to processes
Remote Spawn Testing
ShouldRemoteSpawn() - Testing Distributed Actor Creation
ShouldRemoteSpawn() - Testing Distributed Actor CreationRemote spawn testing allows you to verify that actors correctly create processes on remote nodes in a distributed system. The testing library captures RemoteSpawnEvent operations and provides fluent assertions for validation.
Why Test Remote Spawning:
Distribution Logic: Ensure actors spawn processes on the correct remote nodes
Load Distribution: Verify round-robin or other distribution strategies work correctly
Error Handling: Test behavior when remote nodes are unavailable
Resource Management: Validate that remote spawning respects capacity limits
Example Actor:
Test Implementation:
Advanced Remote Spawn Patterns:
Multi-Node Distribution: Test round-robin or other distribution strategies across multiple nodes
Error Scenarios: Verify proper error handling when nodes are unavailable
Event Inspection: Direct inspection of
RemoteSpawnEventfor detailed validationNegative Assertions: Ensure remote spawns don't happen under certain conditions
Actor Termination Testing
ShouldTerminate() - Testing Actor Lifecycle Completion
ShouldTerminate() - Testing Actor Lifecycle CompletionActor termination is a critical aspect of actor systems. Actors can terminate for various reasons: normal completion, explicit shutdown, or errors. The testing library provides comprehensive tools for validating termination behavior and ensuring proper cleanup.
Why Test Actor Termination:
Resource Cleanup: Ensure actors properly clean up resources when terminating
Error Propagation: Verify that errors are handled correctly and lead to appropriate termination
Graceful Shutdown: Test that actors respond correctly to shutdown signals
Supervision Trees: Validate that supervisors handle child termination appropriately
Termination Reasons:
gen.TerminateReasonNormal- Normal completion of actor workgen.TerminateReasonShutdown- Graceful shutdown requestCustom errors - Abnormal termination due to specific errors
Example Actor:
Test Implementation:
Advanced Termination Patterns:
Exit Signal Testing
ShouldSendExit() - Testing Graceful Process Termination
ShouldSendExit() - Testing Graceful Process TerminationExit signals (SendExit and SendExitMeta) are used to gracefully terminate other processes. This is different from actor self-termination - it's about one actor telling another to exit. The testing library provides comprehensive assertions for validating exit signal behavior.
Why Test Exit Signals:
Graceful Shutdown: Ensure supervisors can properly terminate child processes
Resource Cleanup: Verify that exit signals trigger proper cleanup in target processes
Error Propagation: Test that failure conditions are communicated via exit signals
Supervision Trees: Validate that supervisors manage process lifecycles correctly
Example Actor:
Test Implementation:
Exit Signal Testing Methods
Basic Exit Signal Assertions:
Advanced Exit Signal Patterns:
Cron Testing
ShouldAddCronJob(), ShouldExecuteCronJob() - Testing Scheduled Operations
ShouldAddCronJob(), ShouldExecuteCronJob() - Testing Scheduled OperationsCron job testing allows you to validate scheduled operations in your actors without waiting for real time to pass. The testing library provides comprehensive mock time support and detailed cron job lifecycle management.
Why Test Cron Jobs:
Schedule Validation: Ensure cron expressions are correct and jobs run at expected times
Job Management: Test job addition, removal, enabling, and disabling operations
Execution Logic: Verify that scheduled operations perform correctly when triggered
Time Control: Use mock time to test time-dependent behavior deterministically
Cron Testing Features:
Mock Time Support: Control time flow for deterministic testing
Job Lifecycle Testing: Validate job creation, scheduling, execution, and cleanup
Event Tracking: Monitor all cron-related operations and state changes
Schedule Simulation: Test complex scheduling scenarios without real time delays
Example Actor:
Test Implementation:
Cron Testing Methods
Job Lifecycle Assertions:
Mock Time Control:
Advanced Cron Patterns:
Built-in Assertions
The library includes a comprehensive set of zero-dependency assertion functions that cover common testing scenarios without requiring external testing frameworks:
Why Built-in Assertions:
Zero Dependencies: Avoid version conflicts and complex dependency management
Consistent Interface: All assertions follow the same pattern and error reporting
Testing Framework Agnostic: Works with any Go testing approach
Actor-Specific: Designed specifically for the needs of actor testing
Advanced Features
Dynamic Value Capture - Testing Generated Content
Real-world actors frequently generate dynamic values like timestamps, UUIDs, session IDs, or auto-incrementing counters. Traditional testing approaches struggle with these values because they're unpredictable. The library provides sophisticated capture mechanisms to handle these scenarios elegantly.
The Challenge of Dynamic Values:
Timestamps: Created at runtime, impossible to predict exact values
UUIDs: Randomly generated, different in every test run
Auto-incrementing IDs: Dependent on execution order and system state
Process IDs: Assigned by the actor system, not controllable in tests
The Solution - Value Capture:
Capture Strategies:
Immediate Capture: Capture values as soon as they're generated
Pattern Matching: Use validation functions to identify and validate dynamic content
Structured Matching: Validate message structure while ignoring specific dynamic fields
Cross-Reference Testing: Use captured values in multiple assertions to ensure consistency
Event Inspection - Deep System Analysis
For complex testing scenarios or debugging difficult issues, the library provides direct access to the complete event timeline. This allows you to perform sophisticated analysis of actor behavior beyond what's possible with standard assertions.
Events() - Complete Event History
Events() - Complete Event HistoryAccess all captured events for detailed analysis:
LastEvent() - Most Recent Operation
LastEvent() - Most Recent OperationGet the most recently captured event:
ClearEvents() - Reset Event History
ClearEvents() - Reset Event HistoryClear all captured events, useful for isolating test phases:
Event Inspection Use Cases:
Performance Analysis: Count operations to identify performance bottlenecks
Workflow Validation: Ensure complex multi-step processes execute in the correct order
Error Investigation: Analyze the complete event sequence leading to failures
Integration Testing: Verify that multiple actors interact correctly in complex scenarios
Timeout Support - Assertion Timing Control
The library provides timeout support for assertions that might need time-based validation:
Timeout Function Usage:
Assertion Wrapping: Wrap assertion functions to add timeout behavior
Integration Testing: Useful when testing with external systems that might have delays
Performance Validation: Ensure assertions complete within expected time limits
Testing Patterns and Best Practices
Test Organization Strategies
Single Responsibility Testing: Each test should focus on one specific behavior or scenario. This makes tests easier to understand, debug, and maintain.
State Isolation: Each test should start with a clean state and not depend on other tests. Use actor.ClearEvents() when needed to reset event history between test phases.
Error Path Testing: Don't just test the happy path. Actor systems need robust error handling, so test failure scenarios thoroughly:
Message Design for Testability
Structured Messages: Design your messages to be easily testable by using structured types rather than primitive values:
Predictable vs Dynamic Content: Separate predictable content from dynamic content in your messages to make testing easier:
Performance Testing Considerations
Event Overhead: While event capture is lightweight, be aware that every operation creates events. For performance-critical tests, you can:
Clear events periodically with
ClearEvents()Focus assertions on specific time windows
Use event inspection to identify performance bottlenecks
Scaling Testing: Test how your actors behave under load by simulating multiple concurrent operations:
Best Practices
Use descriptive test names that clearly indicate what behavior is being tested
Test all message types your actor handles, including edge cases
Capture dynamic values early using the
Capture()method for generated IDsTest error conditions not just the happy path
Use pattern matching for complex message validation
Clear events between test phases when needed with
ClearEvents()Configure appropriate log levels for debugging vs production testing
Test temporal behaviors with timeout mechanisms
Validate distributed scenarios using network simulation
Organize tests by behavior rather than by implementation details
This testing library provides comprehensive coverage for all Ergo Framework actor patterns while maintaining zero external dependencies and excellent readability. By following these patterns and practices, you can build robust, well-tested actor systems that behave correctly in both simple and complex scenarios.
Complete Examples and Use Cases
The library includes comprehensive test examples organized into feature-specific files that demonstrate all capabilities through real-world scenarios:
Feature-Based Test Files
basic_test.go - Fundamental Actor Testing
Basic actor functionality and message handling
Dynamic value capture and validation
Built-in assertions and event tracking
Core testing patterns and best practices
network_test.go - Distributed System Testing
Remote node simulation and connectivity
Network configuration and route management
Remote spawn operations and event capture
Multi-node interaction patterns
workflow_test.go - Complex Business Logic
Multi-step order processing workflows
State machine validation and transitions
Business process orchestration
Error handling and recovery scenarios
call_test.go - Synchronous Communication
Call operations and response handling
Async call patterns and timeouts
Send/response communication flows
Concurrent request management
cron_test.go - Scheduled Operations
Cron job lifecycle management
Mock time control and schedule validation
Job execution tracking and assertions
Time-dependent behavior testing
termination_test.go - Actor Lifecycle Management
Actor termination handling and cleanup
Exit signal testing (SendExit/SendExitMeta)
Normal vs abnormal termination scenarios
Resource cleanup validation
Comprehensive Test Examples
Complex State Machine Testing (
workflow_test.go)Multi-step order processing workflow
Validation, payment, and fulfillment pipeline
State transition validation and error handling
Process Management (
basic_test.go)Dynamic worker spawning and management
Resource capacity limits and monitoring
Worker lifecycle (start, stop, restart)
Advanced Pattern Matching (
basic_test.go)Structure matching with partial validation
Dynamic value handling and field validation
Complex conditional message matching
Remote Spawn Testing (
network_test.go)Remote spawn operations on multiple nodes
Round-robin distribution testing
Error handling for unavailable nodes
Event inspection and workflow validation
Cron Job Management (
cron_test.go)Job scheduling and execution validation
Mock time control for deterministic testing
Schedule expression testing and validation
Actor Termination (
termination_test.go)Normal and abnormal termination scenarios
Exit signal testing and process cleanup
Termination reason validation
Post-termination behavior verification
Concurrent Operations (
call_test.go)Multi-client concurrent request handling
Resource contention and capacity management
Load testing and performance validation
Environment & Configuration (
basic_test.go)Environment variable management
Runtime configuration changes
Feature flag and conditional behavior testing
Getting Started with Examples
Learning Path
Start with Basic Examples:
basic_test.go- Core functionality and patternsExplore Message Testing:
basic_test.go- Message flow and assertionsLearn Process Management:
basic_test.go- Spawn operations and lifecycleMaster Synchronous Communication:
call_test.go- Calls and responsesStudy Complex Workflows:
workflow_test.go- Business logic testingPractice Network Testing:
network_test.go- Distributed operationsExplore Scheduling:
cron_test.go- Time-based operationsUnderstand Termination:
termination_test.go- Lifecycle completion
Each test file provides complete, working implementations of specific actor patterns and demonstrates best practices for testing each scenario. All tests include comprehensive comments explaining the testing strategy and validation approach.
Configuration and Environment Testing
Real actors often behave differently based on configuration. Let's test this:
Complex Message Patterns
As your actors become more sophisticated, your message testing needs to handle more complex scenarios:
Testing Message Sequences
Testing Conditional Logic
Basic Process Spawning
Many actors need to create child processes. Here's how to test this:
Capturing Dynamic Process IDs
When actors spawn processes, you often need to use the generated PID in subsequent tests:
Event Inspection for Debugging
When tests fail, you need to understand what actually happened:
Failure Injection Testing
Overview
The Ergo Unit Testing Library includes a failure injection system that allows you to test how your actors handle various error conditions. This is essential for building robust actor systems that can gracefully handle failures in production.
Method Failure Injection
Access failure injection through the actor's Process() method:
Available Failure Methods
The failure injection system provides several methods on TestProcess:
Common Use Cases
Testing Spawn Failures
Testing Message Send Failures
Testing Intermittent Failures
Testing Pattern-Based Failures
Testing One-Time Failures
Advanced Testing Scenarios
Testing Supervisor Restart Strategies
Testing Method Call Tracking
Best Practices
Clear Events Between Test Phases: Use
ClearEvents()when transitioning between test phases to avoid assertion confusion.Test Recovery: Always test that your actors can recover after failures are cleared or when using one-time failures.
Verify Call Counts: Use
GetMethodCallCount()to ensure methods are called the expected number of times.Pattern Matching: Use pattern-based failures to test scenarios where only specific inputs should fail.
Combine with Supervision: Test how supervisors handle child failures by injecting spawn failures during restart attempts.
Common Pitfalls
Event Accumulation: Events accumulate across multiple operations. Use
ClearEvents()to reset between test phases.Timing Issues: Some assertions may need time to complete. Use appropriate timeouts and consider async patterns.
Message Ordering: In high-throughput scenarios, message ordering might not be guaranteed. Test for this explicitly.
State Leakage: Each test should start with clean state. Don't rely on previous test state.
Failure Persistence: Remember that
SetMethodFailurepersists until cleared, whileSetMethodFailureOnceonly fails once.
Conclusion
The Ergo Framework unit testing library provides comprehensive tools for testing actor-based systems. From simple message exchanges to complex distributed workflows, you can validate every aspect of your actor behavior with confidence.
Key Takeaways:
Start Simple: Begin with basic message testing and gradually add complexity
Test Comprehensively: Cover happy paths, error conditions, and edge cases
Use Fluent Assertions: Take advantage of the readable assertion API
Inspect Events: Use event inspection for debugging and understanding actor behavior
Organize Tests: Structure tests by behavior and keep them focused
Handle Async Patterns: Use appropriate timeouts and pattern matching for async operations
The library's zero-dependency design, comprehensive feature set, and integration with Go's testing framework make it the ideal choice for building robust, well-tested actor systems with the Ergo Framework.
Next Steps:
Explore the complete test examples in the framework repository
Start with simple actors and gradually build complexity
Integrate testing into your development workflow
Use the debugging features when tests fail
Share testing patterns with your team
Happy testing!
Last updated
Was this helpful?