Testing
This guide covers strategies for testing code that uses aegis.
Test Helpers
Aegis provides test helpers in the testing subpackage. Import with the testing build tag:
//go:build testing
package mypackage_test
import (
"testing"
aegistesting "github.com/zoobz-io/aegis/testing"
)
Eventually
Wait for a condition with timeout:
func TestNodeStartup(t *testing.T) {
node := createTestNode(t)
node.StartServer()
aegistesting.Eventually(t, func() bool {
return node.IsHealthy()
}, 5*time.Second, 100*time.Millisecond)
}
RequireNoError / RequireError
Assert on errors:
aegistesting.RequireNoError(t, err)
aegistesting.RequireError(t, err)
Creating Test Nodes
For unit tests, create nodes with temporary certificate directories:
func createTestNode(t *testing.T, id string) *aegis.Node {
t.Helper()
certDir := t.TempDir()
node, err := aegis.NewNodeBuilder().
WithID(id).
WithName("Test Node " + id).
WithAddress("localhost:0"). // Random port
WithCertDir(certDir).
Build()
if err != nil {
t.Fatalf("failed to create node: %v", err)
}
t.Cleanup(func() {
node.Shutdown()
})
return node
}
Using localhost:0 lets the OS assign an available port.
Testing Service Providers
To test a service implementation:
func TestIdentityService(t *testing.T) {
// Create provider node
provider, err := aegis.NewNodeBuilder().
WithID("provider").
WithName("Provider").
WithAddress("localhost:0").
WithServices(aegis.ServiceInfo{Name: "identity", Version: "v1"}).
WithServiceRegistration(func(s *grpc.Server) {
identity.RegisterIdentityServiceServer(s, &myIdentityServer{})
}).
WithCertDir(t.TempDir()).
Build()
require.NoError(t, err)
err = provider.StartServer()
require.NoError(t, err)
defer provider.Shutdown()
// Create consumer node with same CA
consumer, err := aegis.NewNodeBuilder().
WithID("consumer").
WithName("Consumer").
WithAddress("localhost:0").
WithCertDir(t.TempDir()). // Different certs won't work!
Build()
require.NoError(t, err)
// ... test the service
}
Important: For nodes to communicate, they must trust the same CA. In tests, either:
- Share the certificate directory
- Use a shared test CA
- Pre-generate certificates
Sharing Test Certificates
Create a helper that generates a shared CA:
func setupTestCA(t *testing.T) string {
t.Helper()
certDir := t.TempDir()
// Generate CA once
_, err := aegis.LoadOrGenerateTLS("test-ca", certDir)
require.NoError(t, err)
return certDir
}
func createNodeWithCA(t *testing.T, id, certDir string) *aegis.Node {
t.Helper()
// Generate node cert using existing CA
_, err := aegis.LoadOrGenerateTLS(id, certDir)
require.NoError(t, err)
node, err := aegis.NewNodeBuilder().
WithID(id).
WithName("Node " + id).
WithAddress("localhost:0").
WithCertDir(certDir).
Build()
require.NoError(t, err)
return node
}
Testing Topology Sync
func TestTopologySync(t *testing.T) {
certDir := setupTestCA(t)
node1 := createNodeWithCA(t, "node-1", certDir)
node2 := createNodeWithCA(t, "node-2", certDir)
node1.StartServer()
node2.StartServer()
defer node1.Shutdown()
defer node2.Shutdown()
// Add node2 as peer of node1
err := node1.AddPeer(aegis.PeerInfo{
ID: "node-2",
Type: aegis.NodeTypeGeneric,
Address: node2.Address,
})
require.NoError(t, err)
// Sync topology
err = node1.SyncTopology(context.Background(), "node-2")
require.NoError(t, err)
// Verify both nodes appear in topology
nodes := node1.Topology.GetAllNodes()
assert.Len(t, nodes, 2)
}
Mocking Services
For unit tests that don't need real gRPC:
type mockIdentityClient struct {
validateResponse *identity.ValidateSessionResponse
validateError error
}
func (m *mockIdentityClient) ValidateSession(ctx context.Context, req *identity.ValidateSessionRequest, opts ...grpc.CallOption) (*identity.ValidateSessionResponse, error) {
return m.validateResponse, m.validateError
}
func TestWithMockedIdentity(t *testing.T) {
mock := &mockIdentityClient{
validateResponse: &identity.ValidateSessionResponse{
Valid: true,
UserId: "user-123",
},
}
// Use mock directly instead of ServiceClient
resp, err := mock.ValidateSession(ctx, &identity.ValidateSessionRequest{Token: "test"})
require.NoError(t, err)
assert.True(t, resp.Valid)
}
Integration Tests
For end-to-end tests with real networking:
//go:build integration
func TestFullMeshCommunication(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
// Create multiple nodes
// Start servers
// Add peers
// Sync topologies
// Make service calls
// Verify responses
}
Run with:
go test -tags=integration ./...
Next Steps
- Troubleshooting — Common errors and solutions
- Services Guide — Registering domain services