akka-net-testing-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Akka.NET Testing Patterns

Akka.NET 测试模式

When to Use This Skill

何时使用该技能

Use this skill when:
  • Writing unit tests for Akka.NET actors
  • Testing persistent actors with event sourcing
  • Verifying actor interactions and message flows
  • Testing actor supervision and lifecycle
  • Mocking external dependencies in actor tests
  • Testing cluster sharding behavior locally
  • Verifying actor state recovery and persistence
在以下场景中使用该技能:
  • 为Akka.NET actors编写单元测试
  • 测试基于事件溯源的持久化actors
  • 验证actor交互与消息流转
  • 测试actor监督机制与生命周期
  • 在actor测试中模拟外部依赖
  • 本地测试集群分片行为
  • 验证actor状态恢复与持久化能力

Reference Files

参考文件

  • examples.md: Complete code samples for all testing patterns (Patterns 1-8 plus Reminders)
  • anti-patterns-and-reference.md: Anti-patterns, traditional TestKit, CI/CD integration
  • examples.md: 所有测试模式的完整代码示例(模式1-8及注意事项)
  • anti-patterns-and-reference.md: 反模式、传统TestKit用法、CI/CD集成指南

Choosing Your Testing Approach

选择合适的测试方法

Use Akka.Hosting.TestKit (Recommended for 95% of Use Cases)

使用Akka.Hosting.TestKit(95%场景推荐)

When:
  • Building modern .NET applications with
    Microsoft.Extensions.DependencyInjection
  • Using Akka.Hosting for actor configuration in production
  • Need to inject services into actors (
    IOptions
    ,
    DbContext
    ,
    ILogger
    , HTTP clients, etc.)
  • Testing applications that use ASP.NET Core, Worker Services, or .NET Aspire
  • Working with modern Akka.NET projects (Akka.NET v1.5+)
Advantages:
  • Native dependency injection support - override services with fakes in tests
  • Configuration parity with production (same extension methods work in tests)
  • Clean separation between actor logic and infrastructure
  • Type-safe actor registry for retrieving actors
适用场景:
  • 基于
    Microsoft.Extensions.DependencyInjection
    构建现代化.NET应用
  • 生产环境中使用Akka.Hosting进行actor配置
  • 需要向actors注入服务(如
    IOptions
    DbContext
    ILogger
    、HTTP客户端等)
  • 测试使用ASP.NET Core、Worker Services或.NET Aspire的应用
  • 基于Akka.NET v1.5+的现代化项目
优势:
  • 原生支持依赖注入 - 可在测试中用伪造服务替代真实服务
  • 与生产环境配置一致(相同的扩展方法可在测试中使用)
  • 清晰分离actor逻辑与基础设施
  • 类型安全的actor注册表用于获取actor引用

Use Traditional Akka.TestKit

使用传统Akka.TestKit

When:
  • Contributing to Akka.NET core library development
  • Working in environments without
    Microsoft.Extensions
    (console apps, legacy systems)
  • Legacy codebases using manual
    Props
    creation without DI
See anti-patterns-and-reference.md for traditional TestKit patterns.

适用场景:
  • 为Akka.NET核心库开发做贡献
  • 工作环境中未使用
    Microsoft.Extensions
    (如控制台应用、遗留系统)
  • 遗留代码库中使用手动创建
    Props
    且未依赖DI
传统TestKit模式请参考anti-patterns-and-reference.md

Core Principles (Akka.Hosting.TestKit)

核心原则(Akka.Hosting.TestKit)

  1. Inherit from
    Akka.Hosting.TestKit.TestKit
    - This is a framework base class, not a user-defined one
  2. Override
    ConfigureServices()
    - Replace real services with fakes/mocks
  3. Override
    ConfigureAkka()
    - Configure actors using the same extension methods as production
  4. Use
    ActorRegistry
    - Type-safe retrieval of actor references
  5. Composition over Inheritance - Fake services as fields, not base classes
  6. No Custom Base Classes - Use method overrides, not inheritance hierarchies
  7. Test One Actor at a Time - Use TestProbes for dependencies
  8. Match Production Patterns - Same extension methods, different
    AkkaExecutionMode

  1. 继承自
    Akka.Hosting.TestKit.TestKit
    - 这是一个框架基类,而非用户自定义类
  2. 重写
    ConfigureServices()
    - 用伪造/模拟服务替换真实服务
  3. 重写
    ConfigureAkka()
    - 使用与生产环境相同的扩展方法配置actors
  4. 使用
    ActorRegistry
    - 类型安全地获取actor引用
  5. 组合优于继承 - 将伪造服务作为字段,而非基类
  6. 不使用自定义基类 - 使用方法重写,而非继承层级
  7. 一次测试一个actor - 为依赖项使用TestProbes
  8. 匹配生产模式 - 使用相同的扩展方法,不同的
    AkkaExecutionMode

Required NuGet Packages

必需的NuGet包

xml
<ItemGroup>
  <!-- Core testing framework -->
  <PackageReference Include="Akka.Hosting.TestKit" Version="*" />

  <!-- xUnit (or your preferred test framework) -->
  <PackageReference Include="xunit" Version="*" />
  <PackageReference Include="xunit.runner.visualstudio" Version="*" />
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />

  <!-- Assertions (recommended) -->
  <PackageReference Include="FluentAssertions" Version="*" />

  <!-- In-memory persistence for testing -->
  <PackageReference Include="Akka.Persistence.Hosting" Version="*" />

  <!-- If testing cluster sharding -->
  <PackageReference Include="Akka.Cluster.Hosting" Version="*" />
</ItemGroup>

xml
<ItemGroup>
  <!-- Core testing framework -->
  <PackageReference Include="Akka.Hosting.TestKit" Version="*" />

  <!-- xUnit (or your preferred test framework) -->
  <PackageReference Include="xunit" Version="*" />
  <PackageReference Include="xunit.runner.visualstudio" Version="*" />
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />

  <!-- Assertions (recommended) -->
  <PackageReference Include="FluentAssertions" Version="*" />

  <!-- In-memory persistence for testing -->
  <PackageReference Include="Akka.Persistence.Hosting" Version="*" />

  <!-- If testing cluster sharding -->
  <PackageReference Include="Akka.Cluster.Hosting" Version="*" />
</ItemGroup>

CRITICAL: File Watcher Fix for Test Projects

关键:测试项目的文件监视器修复

Akka.Hosting.TestKit spins up real
IHost
instances, which by default enable file watchers for configuration reload. When running many tests, this exhausts file descriptor limits on Linux (inotify watch limit).
Add this to your test project - it runs before any tests execute:
csharp
// TestEnvironmentInitializer.cs
using System.Runtime.CompilerServices;

namespace YourApp.Tests;

internal static class TestEnvironmentInitializer
{
    [ModuleInitializer]
    internal static void Initialize()
    {
        // Disable config file watching in test hosts
        // Prevents file descriptor exhaustion (inotify watch limit) on Linux
        Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE", "false");
    }
}
Why this matters:
  • [ModuleInitializer]
    runs automatically before any test code
  • Sets the environment variable globally for all
    IHost
    instances
  • Prevents cryptic
    inotify
    errors when running 100+ tests
  • Also applies to Aspire integration tests that use
    IHost

Akka.Hosting.TestKit会启动真实的
IHost
实例,默认情况下会启用配置重载的文件监视器。当运行大量测试时,这会耗尽Linux系统的文件描述符限制(inotify监视限制)。
在测试项目中添加以下代码 - 它会在所有测试执行前运行:
csharp
// TestEnvironmentInitializer.cs
using System.Runtime.CompilerServices;

namespace YourApp.Tests;

internal static class TestEnvironmentInitializer
{
    [ModuleInitializer]
    internal static void Initialize()
    {
        // Disable config file watching in test hosts
        // Prevents file descriptor exhaustion (inotify watch limit) on Linux
        Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE", "false");
    }
}
为什么这很重要:
  • [ModuleInitializer]
    会在任何测试代码前自动运行
  • 为所有
    IHost
    实例全局设置环境变量
  • 运行100+测试时避免出现模糊的
    inotify
    错误
  • 同样适用于使用
    IHost
    的Aspire集成测试

Testing Patterns Overview

测试模式概述

Each pattern below has a condensed description. See examples.md for complete code samples.
以下每个模式都有简要说明,完整代码示例请参考examples.md

Pattern 1: Basic Actor Test

模式1:基础Actor测试

The foundation pattern. Override
ConfigureServices()
to inject fakes, override
ConfigureAkka()
to register actors with the same extension methods as production.
csharp
public class OrderActorTests : TestKit
{
    private readonly FakeOrderRepository _fakeRepository = new();

    protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
    {
        services.AddSingleton<IOrderRepository>(_fakeRepository);
    }

    protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
    {
        builder.WithInMemoryJournal().WithInMemorySnapshotStore();
        builder.WithActors((system, registry, resolver) =>
        {
            registry.Register<OrderActor>(system.ActorOf(resolver.Props<OrderActor>(), "order-actor"));
        });
    }

    [Fact]
    public async Task CreateOrder_Success_SavesToRepository()
    {
        var orderActor = ActorRegistry.Get<OrderActor>();
        var response = await orderActor.Ask<OrderCommandResult>(
            new CreateOrder("ORDER-123", "CUST-456", 99.99m), RemainingOrDefault);
        response.Status.Should().Be(CommandStatus.Success);
        _fakeRepository.SaveCallCount.Should().Be(1);
    }
}
基础测试模式。重写
ConfigureServices()
注入伪造服务,重写
ConfigureAkka()
使用与生产环境相同的扩展方法注册actors。
csharp
public class OrderActorTests : TestKit
{
    private readonly FakeOrderRepository _fakeRepository = new();

    protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
    {
        services.AddSingleton<IOrderRepository>(_fakeRepository);
    }

    protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
    {
        builder.WithInMemoryJournal().WithInMemorySnapshotStore();
        builder.WithActors((system, registry, resolver) =>
        {
            registry.Register<OrderActor>(system.ActorOf(resolver.Props<OrderActor>(), "order-actor"));
        });
    }

    [Fact]
    public async Task CreateOrder_Success_SavesToRepository()
    {
        var orderActor = ActorRegistry.Get<OrderActor>();
        var response = await orderActor.Ask<OrderCommandResult>(
            new CreateOrder("ORDER-123", "CUST-456", 99.99m), RemainingOrDefault);
        response.Status.Should().Be(CommandStatus.Success);
        _fakeRepository.SaveCallCount.Should().Be(1);
    }
}

Pattern 2: TestProbe for Actor Interactions

模式2:使用TestProbe验证Actor交互

Register a
TestProbe
in the
ActorRegistry
as a stand-in for a dependency actor. Use
ExpectMsgAsync<T>()
to verify messages were sent.
ActorRegistry
中注册
TestProbe
作为依赖actor的替代品。使用
ExpectMsgAsync<T>()
验证消息是否已发送。

Pattern 3: Auto-Responding TestProbe

模式3:自动响应的TestProbe

When the actor under test uses
Ask
to communicate with dependencies, create an auto-responder actor that forwards messages to a probe AND replies to avoid timeouts.
当被测actor使用
Ask
与依赖项通信时,创建一个自动响应actor,将消息转发给probe并回复,避免超时。

Pattern 4: Testing Persistent Actors

模式4:测试持久化Actor

Use
WithInMemoryJournal()
and
WithInMemorySnapshotStore()
. Test recovery by killing the actor with
PoisonPill
and querying to force recovery from journal.
使用
WithInMemoryJournal()
WithInMemorySnapshotStore()
。通过发送
PoisonPill
杀死actor并查询状态,验证其从日志中恢复的能力。

Pattern 5: Reuse Production Configuration

模式5:复用生产环境配置

Always reuse production extension methods in tests instead of duplicating HOCON config. This ensures tests use the exact same configuration as production.
csharp
protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
{
    builder
        .AddDraftSerializer()                                    // Same as production
        .AddOrderDomainActors(AkkaExecutionMode.LocalTest)      // Same, but local mode
        .WithInMemoryJournal().WithInMemorySnapshotStore();      // Test-specific overrides
}
始终在测试中复用生产环境的扩展方法,而非重复编写HOCON配置。这确保测试使用与生产环境完全相同的配置。
csharp
protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
{
    builder
        .AddDraftSerializer()                                    // 与生产环境一致
        .AddOrderDomainActors(AkkaExecutionMode.LocalTest)      // 配置相同,模式为本地测试
        .WithInMemoryJournal().WithInMemorySnapshotStore();      // 测试专属覆盖配置
}

Pattern 6: Cluster Sharding Locally

模式6:本地测试集群分片

Use
AkkaExecutionMode.LocalTest
with
GenericChildPerEntityParent
to test sharding behavior without an actual cluster. Same extension methods, different mode.
结合
AkkaExecutionMode.LocalTest
GenericChildPerEntityParent
,无需真实集群即可测试分片行为。使用相同的扩展方法,仅模式不同。

Pattern 7: AwaitAssertAsync for Async Operations

模式7:使用AwaitAssertAsync处理异步操作

Use
AwaitAssertAsync
when actors perform async operations. It retries assertions until they pass or timeout, preventing flaky tests.
csharp
await AwaitAssertAsync(() =>
{
    _fakeReadModelService.SyncCallCount.Should().BeGreaterOrEqualTo(1);
}, TimeSpan.FromSeconds(3));
当actors执行异步操作时,使用
AwaitAssertAsync
。它会重试断言直到通过或超时,避免不稳定的测试。
csharp
await AwaitAssertAsync(() =>
{
    _fakeReadModelService.SyncCallCount.Should().BeGreaterOrEqualTo(1);
}, TimeSpan.FromSeconds(3));

Pattern 8: Scenario-Based Integration Tests

模式8:基于场景的集成测试

Test complete business workflows end-to-end with multiple actors and state transitions. Register all domain actors, verify state at each step.

端到端测试完整业务工作流,涉及多个actors与状态转换。注册所有领域actors,验证每个步骤的状态。

Common Patterns Summary

常见模式汇总

PatternUse Case
Basic Actor TestSingle actor with injected services
TestProbeVerify actor sends messages to dependencies
Auto-ResponderAvoid
Ask
timeouts when testing
Persistent ActorTest event sourcing and recovery
Cluster ShardingTest sharding behavior locally
AwaitAssertAsyncHandle async operations in actors
Scenario TestsEnd-to-end business workflows

模式适用场景
基础Actor测试带有注入服务的单个actor
TestProbe用法验证actor向依赖项发送消息
自动响应器测试时避免
Ask
超时
持久化Actor测试测试事件溯源与恢复能力
集群分片测试本地测试分片行为
AwaitAssertAsync处理actor中的异步操作
场景测试端到端业务工作流测试

Best Practices

最佳实践

  1. One test class per actor - Keep tests focused
  2. Override ConfigureServices/ConfigureAkka - Don't create base classes
  3. Use fakes, not mocks - Simpler, more maintainable
  4. Test one actor at a time - Use TestProbes for dependencies
  5. Match production patterns - Same extension methods, different
    AkkaExecutionMode
  6. Use AwaitAssertAsync for async - Prevents flaky tests
  7. Test recovery - Kill and restart actors to verify persistence
  8. Scenario tests for workflows - Test complete business flows end-to-end
  9. Keep tests fast - In-memory persistence, no real databases
  10. Use meaningful names -
    Scenario_FirstTimePurchase_SuccessfulPayment

  1. 每个actor对应一个测试类 - 保持测试聚焦
  2. 重写ConfigureServices/ConfigureAkka - 不要创建基类
  3. 使用伪造服务而非模拟对象 - 更简单、更易维护
  4. 一次测试一个actor - 为依赖项使用TestProbes
  5. 匹配生产环境模式 - 使用相同的扩展方法,不同的
    AkkaExecutionMode
  6. 异步操作使用AwaitAssertAsync - 避免不稳定测试
  7. 测试恢复能力 - 杀死并重启actor以验证持久化
  8. 工作流使用场景测试 - 端到端测试完整业务流程
  9. 保持测试快速 - 使用内存持久化,不连接真实数据库
  10. 使用有意义的命名 - 例如
    Scenario_FirstTimePurchase_SuccessfulPayment

Debugging Tips

调试技巧

  1. Enable debug logging - Pass
    LogLevel.Debug
    to TestKit constructor
  2. Use ITestOutputHelper - See actor system logs in test output
  3. Inspect TestProbe - Check
    probe.Messages
    to see what was sent
  4. Query actor state - Add state query messages for debugging
  5. Use AwaitAssertAsync with logging - See why assertions fail
  6. Check ActorRegistry - Verify actors are registered correctly
csharp
// Constructor with debug logging
public OrderActorTests(ITestOutputHelper output)
    : base(output: output, logLevel: LogLevel.Debug)
{
}
  1. 启用调试日志 - 向TestKit构造函数传入
    LogLevel.Debug
  2. 使用ITestOutputHelper - 在测试输出中查看actor系统日志
  3. 检查TestProbe - 查看
    probe.Messages
    确认发送的消息
  4. 查询actor状态 - 添加状态查询消息用于调试
  5. 结合日志使用AwaitAssertAsync - 查看断言失败的原因
  6. 检查ActorRegistry - 验证actors是否正确注册
csharp
// 带调试日志的构造函数
public OrderActorTests(ITestOutputHelper output)
    : base(output: output, logLevel: LogLevel.Debug)
{
}