Actor Testing¶
Conventions for testing Akka.NET actors. Examples assume xUnit.
Choosing a TestKit¶
| Scenario | Use |
|---|---|
| Actor with DI dependencies, or resolved through the actor registry | Akka.Hosting.TestKit |
| Simple actor with no DI | Classic Akka.TestKit.Xunit |
| Pure state logic | No TestKit, see State-only tests |
Register TestProbes in DI¶
Actors resolve collaborators through the container or registry, so register probes there instead of passing actor refs through constructors.
public sealed class DownloadManagerTests : Akka.Hosting.TestKit.TestKit
{
private readonly TestProbe _scheduler;
public DownloadManagerTests() { _scheduler = CreateTestProbe("scheduler"); }
protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
{
builder.WithActors((system, registry) =>
{
registry.Register<DownloadScheduler>(_scheduler);
var props = DependencyResolver.For(system).Props<DownloadManager>();
registry.Register<DownloadManager>(system.ActorOf(props, "manager"));
});
}
}
State-only unit tests¶
If actor state lives in its own class with Apply(event) methods, test that class
directly. No actor system, no timing, fast and deterministic.
[Fact]
public void Apply_Paused_SetsPausedFlag()
{
var state = DownloadState.Empty;
state.Apply(new Paused(Id));
Assert.True(state.IsPaused);
}
Cover GetSnapshot() and persistence round-trips (GetPersistenceState() then
FromPersistence()) the same way.
Interaction flows¶
For multi-step flows, chain ExpectMsg calls on probes. Each call is a bounded
wait and an assertion in one.
manager.Tell(new AddDownload(id, url), TestActor);
var added = ExpectMsg<AddDownloadCompleted>();
Assert.Equal(id, added.DownloadId);
var dispatched = _scheduler.ExpectMsg<WorkAvailable>();
Assert.Equal(id, dispatched.DownloadId);
_scheduler.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
Timeouts¶
Never use Thread.Sleep or Task.Delay to wait for an actor
Use ExpectMsg, AwaitAssert, or ExpectNoMsg with an explicit duration.
Timeouts belong on asynchronous operations only; they bound the wait, they
are not a way to "let things settle".
Mocking rules¶
- Plain interfaces (
IDataFiles,IHttpClientFactory, clocks, stores): use NSubstitute. - Actors: never mock. Use
TestProbeor the real actor.
var files = Substitute.For<IDataFiles>();
files.Exists(Arg.Any<string>()).Returns(true);
// ...
files.Received(1).Exists(Arg.Is<string>(p => p.EndsWith("state.json")));
Persistence
Use the in-memory journal and snapshot store for persistent actors, and recover by stopping and re-creating the actor with the same persistence id.