0

I'm trying to unit test my state machine and that it transitions to the states I expect when certain events are received. To do this I'm using the InMemoryTestHarness which enables me to create my state machine and then using the bus I can publish messages to it.

For the most part this is fine. However, there are a couple of examples where it then goes off and does an activity before it transitions state like this:

During(Submitted,
    When(OnFlyCoolThingRequest)
        .Activity(activitySelector => activitySelector.OfType<MakeItFlyActivity>()).TransitionTo(Flying));

At this point I don't want to test the activity and as it happens it appears that it seems to actually break the test (just hangs). My test is currently setup like this:

    [Test]
    public async Task CoolThingSagaShouldTransitionToFlyingStateOnFlyCoolThingRequestEvent()
    {
        var stateMachine = new CoolStateMachine();
        var harness = new InMemoryTestHarness();
        var saga = harness.StateMachineSaga<CoolThingSaga, CoolStateMachine>(new CoolStateMachine());

        await harness.Start();

        try
        {
            var coolThing = GetCoolThing();

            // Create a CoolThing instance.
            await harness.Bus.Publish(new CoolThingCreated(coolThing.Id));
            var instanceIdSubmitted = await saga.Exists(coolThing.Id, state => state.Submitted);

            // Publish a FlyCoolThingRequest event for the above instance.
            await harness.Bus.Publish(new FlyCoolThingRequest(coolThing.Id, DateTime.UtcNow));
            var instanceIdFlying = await saga.Exists(coolThing.Id, state => state.Flying);
            Assert.That(instanceIdSubmitted.Value, Is.EqualTo(instanceIdFlying.Value), $"Instance id returned between the state of '{stateMachine.Submitted.Name}' and '{stateMachine.Flying.Name}' should be the same.");
            Assert.That(instanceIdFlying, Is.Not.Null, $"A CoolThingSaga instance should have been created with Id {instanceIdFlying} and a be in the state of '{stateMachine.Flying}'.");
        }
        finally
        {
            await harness.Stop();
        }
    }

At the moment this type of test works for testing states that don't have activities associated with them. However, the tests seem to hang when an activity is involved. So how can I mock the activity so it doesn't actually try and perform that logic?

UPDATE

Looks like it fails to create the activity due to not being able to find a parameterless constructor. My activity is akin to this:

public sealed class MakeItFlyActivity : Activity<CoolThingSaga, FlyCoolThingRequest>
{
    private readonly FlyingService _flyingService;

    public MakeItFlyActivity(FlyingService flyingService)
    {
        _flyingService = flyingService;
    }

    public async Task Execute(BehaviorContext<CoolThingSaga, FlyCoolThingRequest> context, Behavior<CoolThingSaga, FlyCoolThingRequest> next)
    {
        await flyingService.MakeItFly(context.Instance.Details.Id);
        await next.Execute(context);
    }

    public void Accept(StateMachineVisitor visitor) => visitor.Visit(this);

    public async Task Faulted<TException>(BehaviorExceptionContext<CoolThingSaga, FlyCoolThingRequest, TException> context, Behavior<CoolThingSaga, FlyCoolThingRequest> next)
        where TException : Exception => await next.Faulted(context);

    public void Probe(ProbeContext context) => context.CreateScope(nameof(MakeItFlyActivity));
}

My setup is as follows:

private InMemoryTestHarness fHarness;
private StateMachineSagaTestHarness<CoolThingSaga, CoolStateMachine> fSaga;
private CoolStateMachine fStateMachine;

[OneTimeSetUp]
public async Task Setup()
{
    var provider = new ServiceCollection()
        .AddSingleton<ILoggerFactory>(p => new TestOutputLoggerFactory(true))
        .AddMassTransitInMemoryTestHarness(cfg =>
        {
            cfg.AddSagaStateMachine<CoolStateMachine, CoolThingSaga>().InMemoryRepository();
            cfg.AddSagaStateMachineTestHarness<CoolStateMachine, CoolThingSaga>();
        })
        .AddSingleton(p => new FlyingService(TimeSpan.FromMinutes(15), NullLogger<FlyingService>.Instance))
        .AddScoped<MakeItFlyActivity>()
        .BuildServiceProvider(true);

    fHarness = provider.GetRequiredService<InMemoryTestHarness>();
    fSaga = fHarness.StateMachineSaga<CoolThingSaga, CoolStateMachine>(new CoolStateMachine());
    fStateMachine = provider.GetRequiredService<CoolStateMachine>();

    await fHarness.Start();
}
sr28
  • 4,728
  • 5
  • 36
  • 67

1 Answers1

0

To test state machines, you should be using the container based test harness as outlined in the documentation. This will ensure that activities and their dependencies can be resolved at runtime. Your test likely hangs because the activity could not be created.

The documentation for v8 is linked, you can also refer to the v7 documentation if you're using an earlier version.

Chris Patterson
  • 28,659
  • 3
  • 47
  • 59
  • You're right that the activity couldn't be created. I get a 'No parameterless constructor defined for type 'MakeItFlyActivity', as I'm trying to inject something into that activity. I'll update my question with further details – sr28 Apr 12 '22 at 15:49
  • I don't understand what's wrong with my setup that means the activity can't resolve. – sr28 Apr 12 '22 at 16:25