0

I am trying to create unit tests for my state machine to check if each state does what it is supposed to do.

I can bring the state machine into any state by publishing the event that starts the state machine and creating all the variables that I need to reach the desired state.

Simplified state machine:

 public class CheckFilesStateMachine : MassTransitStateMachine<DataState>, ICheckFilesStateMachine
    {
        #region Events
        public Event<IEventCheckFile> CheckFile { get; private set; }
        #endregion

        #region States
        
        public State CheckingForFiles { get; }

        public State ValidatingFiles { get; }

        public State NoFilesFound { get; }

        public State Validated { get; }

        public State FilesProcessed { get; }

        public State ValidationFailed{ get; }

        #endregion

        public DataStateMachine(IDataService dataService)
        {
            InstanceState(x => x.CurrentState);

            Event(() => CheckFile, x => x.CorrelateById(context => context.Message.Id));

            Initially(When(CheckFile).TransitionTo(CheckingForFiles));

            WhenEnter(CheckingForFiles, binder => binder
                .Then(x => x.Instance.Files= dataService.GetFiles())
                .IfElse(x => dataService.HasFiles(),
                        x => x.TransitionTo(ValidatingFiles),
                        x => x.TransitionTo(NoFilesFound))
                .Catch<Exception>(ex => ex
                    .Then(x => Console.WriteLine(x.Exception.Message))));

            WhenEnter(ValidatingFiles, binder => binder
                .IfElse(x => dataService.ValidateFiles(),
                        x => x.TransitionTo(Validated),
                        x => x.TransitionTo(ValidationFailed))
                .Catch<Exception>(ex => ex
                    .Then(x => Console.WriteLine(x.Exception.Message))));

            WhenEnter(NoFilesFound, binder => binder.Then(x => Console.WriteLine("No Files found")).Finalize());
            WhenEnter(Validated, binder => binder.Then(x => Console.WriteLine("Validated")).Finalize());
            WhenEnter(ValidationFailed, binder => binder.Then(x => Console.WriteLine("Validation Failed")).Finalize());

            SetCompletedWhenFinalized();
        }
    }

I wrote some unit tests and I can start the machine (followed this guide from the MassTransit Documentation), but I want to check if the states CheckingForFiles and ValidatingFiles are doing what they are supposed to do independent of each other.

So far, I can mock the IDataService methods to reach the desired state by following the flow of the state machine, but is there a way to start the state machine and just jump to a specific state?

UPDATE

Let's say I have another state in there:

 public class CheckFilesStateMachine : MassTransitStateMachine<DataState>, ICheckFilesStateMachine
    {
        #region Events
        public Event<IEventCheckFile> CheckFile { get; private set; }
        #endregion

        #region States
        
        public State CheckingForFiles { get; }

        public State ValidatingFiles { get; }

        public State DeletingFiles{ get; }

        public State NoFilesFound { get; }

        public State Validated { get; }

        public State FilesProcessed { get; }

        public State ValidationFailed{ get; }

        #endregion

        public DataStateMachine(IDataService dataService)
        {
            InstanceState(x => x.CurrentState);

            Event(() => CheckFile, x => x.CorrelateById(context => context.Message.Id));

            Initially(When(CheckFile).TransitionTo(CheckingForFiles));

            WhenEnter(CheckingForFiles, binder => binder
                .Then(x => x.Instance.Files= dataService.GetFiles())
                .IfElse(x => dataService.HasFiles(),
                        x => x.TransitionTo(ValidatingFiles),
                        x => x.TransitionTo(NoFilesFound))
                .Catch<Exception>(ex => ex
                    .Then(x => Console.WriteLine(x.Exception.Message))));

            WhenEnter(ValidatingFiles, binder => binder
                .IfElse(x => dataService.ValidateFiles(),
                        x => x.TransitionTo(Validated),
                        x => x.TransitionTo(DeletingFiles))
                .Catch<Exception>(ex => ex
                    .Then(x => Console.WriteLine(x.Exception.Message))));

            WhenEnter(DeletingFiles, binder => binder
                .Then(x => x.Instance.Files= dataService.RemoveFiles())
                .TransitionTo(ValidationFailed)
                .Catch<Exception>(ex => ex
                    .Then(x => Console.WriteLine(x.Exception.Message))));

            WhenEnter(NoFilesFound, binder => binder.Then(x => Console.WriteLine("No Files found")).Finalize());
            WhenEnter(Validated, binder => binder.Then(x => Console.WriteLine("Validated")).Finalize());
            WhenEnter(ValidationFailed, binder => binder.Then(x => Console.WriteLine("Validation Failed")).Finalize());

            SetCompletedWhenFinalized();
        }
    }

In this case, can I enter in CheckingForFiles and then jump to DeletingFiles? Is it possible?

Daniel
  • 91
  • 1
  • 14

1 Answers1

0

If you are using the container-based in-memory test harness, you can resolve the saga dictionary from the container and add an instance:

var dictionary = provider.GetRequiredService<IndexedSagaDictionary<DataState>>();

dictionary.Add(new SagaInstance<DataState>(new DataState() 
{
    CorrelationId = dataId,
    CurrentState = "CheckingForFiles" 
}));
Chris Patterson
  • 28,659
  • 3
  • 47
  • 59
  • This raises another two questions for me. First, how do I start the machine in this case? By simply publishing the event? Secondly, I updated the initial question. Thank you! – Daniel Jul 29 '21 at 15:27
  • Yes, publish the event. Look at the [Season 2 Sample](https://github.com/MassTransit/Sample-Library/blob/master/tests/Library.Components.Tests/ReservationStateMachine_Specs.cs#L125) for examples of how that can be accomplished. – Chris Patterson Jul 29 '21 at 15:31
  • Publishing the event seems to start the state machine without keeping track of the options presented through the IndexedSagaDictionary. – Daniel Jul 29 '21 at 15:40
  • If you [followed this example](https://masstransit-project.com/usage/testing.html#state-machine-saga-2) and your event correlates to the proper saga instance, it should be there. – Chris Patterson Jul 29 '21 at 15:44
  • It worked for my case. – mrtkprc May 25 '23 at 15:05