I'm trying to test an Actor. On one scenario I want to focus on how it processes a message. For various design reasons, I want to split up an action in to 2 separate distinct steps / messages for the same actor. Mainly, it is possible that the 2nd stage (i.e. the second message) will not be able to be processed and may have to be rescheduled. So for my unit tests, I want to focus on the stage 1 behavior, but using Test Kit it automatically processes the message, because I call this this.Self.Tell("2");
.
I've simplified the code and put it here. The following is the actor code:
public class TestSelfActor : UntypedActor
{
protected override void OnReceive(object message)
{
switch (message)
{
case string s:
if (s == "1")
{
Console.WriteLine("one");
this.Self.Tell("2");
this.Sender.Tell("test");
}
else if (s == "2") { Console.WriteLine("two"); }
break;
}
}
}
Here is the test method
public void TestSendingMessage()
{
target = Sys.ActorOf(Props.Create(() => new TestSelfActor()));
target.Tell("1");
actual = this.ExpectMsg<string>();
Assert.AreEqual("test", actual);
}
If I run this, it will pass, and the output will show
one
two
Ideally, there is some way to setup the test so I can send it the first message and then assert that it told itself the message "2", without it every outputting to the console "2".
Some extra info, I'm running .NET Framework 4.8, and akka/akka.testkit 1.3.8, although I think the code and problem are simple enough that it doesn't matter the version?
In your test project you can inherit from TestSelfActor and override AroundReceive method (then you can fake it with NSubstitute, Moq, ...), In AroundReceive you can intercept messages and decide how to handle them (e.g. return false for "2" in your case) Below is example using XUnit and NSubstitute. I could also extend it and create fake MessageHandler in XUnit fixture to make it reusable. You can always pass null if you want to test default behavior. Hope this helps
using Akka.Actor;
using Akka.TestKit.Xunit;
using NSubstitute;
using System;
using Xunit;
namespace ActorTests
{
public class TestSelfActor : UntypedActor
{
protected override void OnReceive(object message)
{
switch (message)
{
case string s:
if (s == "1")
{
Console.WriteLine("one");
this.Self.Tell("2");
this.Sender.Tell("test");
}
else if (s == "2") { Console.WriteLine("two"); }
break;
}
}
}
public interface IMessageHandler
{
Func<object, bool> AroundReceiveHandler { get; }
}
public class FakeActor : TestSelfActor
{
private readonly IMessageHandler _messageHandler;
public FakeActor(IMessageHandler messageHandler)
{
_messageHandler = messageHandler;
}
protected override bool AroundReceive(Receive receive, object message)
{
var acceptMessage = _messageHandler?.AroundReceiveHandler?.Invoke(message) ?? true;
return acceptMessage && base.AroundReceive(receive, message);
}
}
public class SelfActorSpecs : TestKit
{
[Fact]
public void When_Sender_TellsOne_Actor_Should_RespondWith_Test_NotSending_Two_ToSelf()
{
var messageHandler = Substitute.For<IMessageHandler>();
messageHandler.AroundReceiveHandler(Arg.Any<string>()).Returns(true);
messageHandler.AroundReceiveHandler("2").Returns(false);
var target = Sys.ActorOf(Props.Create(() => new FakeActor(messageHandler)));
target.Tell("1");
ExpectMsg<string>(x => x == "test");
}
}
}