# 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 `IServiceProvider` or `IOptions` ## When Not to Use - Simple actor unit tests that don't need DI — use Akka.TestKit directly (see [16-TestKit.md](./16-TestKit.md)) - Multi-node cluster tests — use MultiNodeTestRunner (see [18-MultiNodeTestRunner.md](./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: ```csharp 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(); services.AddSingleton>( Options.Create(TestSiteConfig.TwoDevices())); } protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) { builder .WithActors((system, registry, resolver) => { var manager = system.ActorOf( resolver.Props(), "device-manager"); registry.Register(manager); }); } [Fact] public async Task DeviceManager_should_create_device_actors_from_config() { var manager = await ActorRegistry.GetAsync(); manager.Tell(new GetManagedDevices()); var devices = ExpectMsg(); Assert.Equal(2, devices.Devices.Count); } } ``` ### Mock Protocol Adapters Register mock protocol adapters that simulate equipment behavior without requiring actual network connections: ```csharp 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: ```csharp protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) { // Load test configuration var config = new ConfigurationBuilder() .AddJsonFile("test-appsettings.json") .Build(); services.Configure(config.GetSection("ScadaSite")); } ``` ## Common Patterns ### ActorRegistry Assertions Use `ActorRegistry.GetAsync()` to verify actors were correctly registered during startup: ```csharp [Fact] public async Task Should_register_all_required_actors() { var manager = await ActorRegistry.GetAsync(); Assert.NotNull(manager); var health = await ActorRegistry.GetAsync(); Assert.NotNull(health); } ``` ### Testing with TestProbe TestProbe works alongside Hosting.TestKit: ```csharp [Fact] public async Task DeviceManager_should_forward_commands() { var probe = CreateTestProbe(); var manager = await ActorRegistry.GetAsync(); // 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(cmd => cmd.CommandId == "cmd-1"); } ``` ### Lifecycle Testing Verify that the actor system starts and stops cleanly: ```csharp [Fact] public async Task Should_shutdown_gracefully() { var manager = await ActorRegistry.GetAsync(); 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 ``` ## References - NuGet: - Petabridge Bootcamp: