首页 > 解决方案 > How to use TestKit to not process messages sent to Self

问题描述

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?

标签: c#akka.netakka-testkit

解决方案


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");
        }
    }
}


推荐阅读