Notes and documentation covering actors, remoting, clustering, persistence, streams, serialization, hosting, testing, and best practices for the Akka.NET framework used throughout the ScadaLink system.
6.3 KiB
17 — Akka.Hosting.TestKit
Overview
Akka.Hosting.TestKit extends the standard TestKit with full Microsoft.Extensions.Hosting integration. It spins up a complete host environment with DI, logging, configuration, and Akka.NET — making it ideal for integration tests that need the full application wiring.
In the SCADA system, Hosting.TestKit is used to test the full actor startup pipeline: DI-injected actors, protocol adapter factories, configuration loading, and the Device Manager singleton lifecycle — all within a controlled test environment.
When to Use
- Integration tests that require DI (protocol adapter factories, configuration services, loggers)
- Testing the full Akka.Hosting configuration pipeline (Remoting, Cluster, Singleton registration)
- Verifying that the ActorRegistry correctly resolves actor references
- Testing actors that depend on
IServiceProviderorIOptions<T>
When Not to Use
- Simple actor unit tests that don't need DI — use Akka.TestKit directly (see 16-TestKit.md)
- Multi-node cluster tests — use MultiNodeTestRunner (see 18-MultiNodeTestRunner.md)
- Performance tests — the hosting overhead adds latency that distorts benchmarks
Design Decisions for the SCADA System
Test Fixture Structure
Each integration test class inherits from Akka.Hosting.TestKit.TestKit and overrides configuration methods:
public class DeviceManagerIntegrationTests : Akka.Hosting.TestKit.TestKit
{
public DeviceManagerIntegrationTests(ITestOutputHelper output)
: base(output: output) { }
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
// Register test doubles for protocol adapters
services.AddSingleton<IProtocolAdapterFactory, MockProtocolAdapterFactory>();
services.AddSingleton<IOptions<SiteConfiguration>>(
Options.Create(TestSiteConfig.TwoDevices()));
}
protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
{
builder
.WithActors((system, registry, resolver) =>
{
var manager = system.ActorOf(
resolver.Props<DeviceManagerActor>(), "device-manager");
registry.Register<DeviceManagerActor>(manager);
});
}
[Fact]
public async Task DeviceManager_should_create_device_actors_from_config()
{
var manager = await ActorRegistry.GetAsync<DeviceManagerActor>();
manager.Tell(new GetManagedDevices());
var devices = ExpectMsg<ManagedDeviceList>();
Assert.Equal(2, devices.Devices.Count);
}
}
Mock Protocol Adapters
Register mock protocol adapters that simulate equipment behavior without requiring actual network connections:
public class MockProtocolAdapterFactory : IProtocolAdapterFactory
{
public Props CreateAdapterProps(DeviceConfig config, IDependencyResolver resolver)
{
return Props.Create(() => new MockDeviceActor(config));
}
}
Testing Configuration Loading
Verify that site-specific configuration is correctly loaded and applied:
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
// Load test configuration
var config = new ConfigurationBuilder()
.AddJsonFile("test-appsettings.json")
.Build();
services.Configure<SiteConfiguration>(config.GetSection("ScadaSite"));
}
Common Patterns
ActorRegistry Assertions
Use ActorRegistry.GetAsync<T>() to verify actors were correctly registered during startup:
[Fact]
public async Task Should_register_all_required_actors()
{
var manager = await ActorRegistry.GetAsync<DeviceManagerActor>();
Assert.NotNull(manager);
var health = await ActorRegistry.GetAsync<ClusterHealthActor>();
Assert.NotNull(health);
}
Testing with TestProbe
TestProbe works alongside Hosting.TestKit:
[Fact]
public async Task DeviceManager_should_forward_commands()
{
var probe = CreateTestProbe();
var manager = await ActorRegistry.GetAsync<DeviceManagerActor>();
// Tell the manager to use the probe for a specific device
manager.Tell(new RegisterTestDevice("test-device", probe));
manager.Tell(new SendCommand("cmd-1", "test-device", "Start", true));
probe.ExpectMsg<SendCommand>(cmd => cmd.CommandId == "cmd-1");
}
Lifecycle Testing
Verify that the actor system starts and stops cleanly:
[Fact]
public async Task Should_shutdown_gracefully()
{
var manager = await ActorRegistry.GetAsync<DeviceManagerActor>();
Watch(manager);
// Trigger coordinated shutdown
await CoordinatedShutdown.Get(Sys).Run(CoordinatedShutdown.ClrExitReason.Instance);
ExpectTerminated(manager);
}
Anti-Patterns
Duplicating Production Configuration
Test configuration should be minimal and focused on the test scenario. Do not copy the entire production Akka.Hosting setup into tests — only configure what the test needs. This prevents tests from breaking when production config changes.
Testing Cluster Behavior in Hosting.TestKit
Hosting.TestKit runs a single-node ActorSystem. Do not test cluster membership, failover, or Singleton migration here — those require MultiNodeTestRunner. Hosting.TestKit is for testing application-level actor behavior with DI, not distributed system behavior.
Slow Tests from Full Hosting Stack
Each Hosting.TestKit test spins up a full host. If you have hundreds of tests, this adds up. Reserve Hosting.TestKit for integration tests that genuinely need DI; use plain TestKit for unit tests.
Configuration Guidance
NuGet Package
Akka.Hosting.TestKit
This pulls in Akka.Hosting, Akka.TestKit.Xunit2, and Microsoft.Extensions.Hosting.Testing.
Test Project Structure
ScadaSystem.Tests/
Unit/
DeviceActorTests.cs (plain TestKit)
CommandHandlerTests.cs (plain TestKit)
Integration/
DeviceManagerIntegTests.cs (Hosting.TestKit)
ConfigurationIntegTests.cs (Hosting.TestKit)
Fixtures/
TestSiteConfig.cs
MockProtocolAdapterFactory.cs