user247702
user247702

Reputation: 24212

Why does this FSM only succeed at one state transition?

I have a very simple FSM that succeeds at doing one transition, but no more than that. I don't know if there's an error in the FSM or if there's an error in the test class.

Here is a full reproducible example with unit tests:

using Akka;
using Akka.Actor;
using Akka.TestKit;
using Akka.TestKit.Xunit;
using System.Diagnostics;
using Xunit;

class MyFsm : FSM<MyFsm.State, MyFsm.Data>
{
    public MyFsm()
    {
        StartWith(State.Idle, new Data());

        When(State.Idle, state =>
        {
            var eventWasHandled = state.FsmEvent.Match()
                .With<MessageA>(message => { return; })
                .WasHandled;

            if (eventWasHandled)
            {
                Debug.WriteLine($"{State.Idle} => transitioning to {State.Busy}");
                return GoTo(State.Busy);
            }
            else
            {
                Debug.WriteLine($"{State.Idle} => returning null");
                return null;
            }
        });

        When(State.Busy, state =>
        {
            var eventWasHandled = state.FsmEvent.Match()
                .With<MessageB>(message => { return; })
                .WasHandled;

            if (eventWasHandled)
            {
                Debug.WriteLine($"{State.Busy} => transitioning to {State.Done}");
                return GoTo(State.Done);
            }
            else
            {
                Debug.WriteLine($"{State.Busy} => returning null");
                return null;
            }
        });

        Initialize();
    }

    public enum State { Idle, Busy, Done }

    public class Data { }
}

class MessageA { }

class MessageB { }

public class MyFsmTests : TestKit
{
    [Fact]
    public void Its_initial_state_is_Idle()
    {
        var myFsm = new TestFSMRef<MyFsm, MyFsm.State, MyFsm.Data>(Sys, Props.Create<MyFsm>());

        Assert.Equal(MyFsm.State.Idle, myFsm.StateName);
    }

    [Fact]
    public void It_transitions_to_the_Busy_state_after_receiving_MessageA()
    {
        var myFsm = new TestFSMRef<MyFsm, MyFsm.State, MyFsm.Data>(Sys, Props.Create<MyFsm>());
        myFsm.SetState(MyFsm.State.Idle);

        myFsm.Tell(new MessageA());

        Assert.Equal(MyFsm.State.Busy, myFsm.StateName);
    }

    [Fact]
    public void It_transitions_to_the_Done_state_after_receiving_MessageB_using_SetState()
    {
        var myFsm = new TestFSMRef<MyFsm, MyFsm.State, MyFsm.Data>(Sys, Props.Create<MyFsm>());
        myFsm.SetState(MyFsm.State.Busy);

        myFsm.Tell(new MessageB());

        Assert.Equal(MyFsm.State.Done, myFsm.StateName);
    }

    [Fact]
    public void It_transitions_to_the_Done_state_after_receiving_MessageB_without_using_SetState()
    {
        var myFsm = new TestFSMRef<MyFsm, MyFsm.State, MyFsm.Data>(Sys, Props.Create<MyFsm>());

        myFsm.Tell(new MessageA());
        myFsm.Tell(new MessageB());

        Assert.Equal(MyFsm.State.Done, myFsm.StateName);
    }
}

I've gone through the docs multiple times and can't find any glaring error in the code. What am I missing?

Upvotes: 3

Views: 138

Answers (1)

Aaronontheweb
Aaronontheweb

Reputation: 8394

Looks like the issue here is that you don't actually have a state defined as Done, as far as this FSM knows.

You need to add a When(State.Done, e => { ... }) handler and that will allow the FSM to transition to that behavior and report it correctly.

Upvotes: 3

Related Questions