I'm developing an application in C# using MassTransit's saga state machine feature, and I'm trying to write unit tests (NUnit) for my saga to assert that certain events are ignored in certain states.
My approach so far has been to put the saga in the state, publish the event it should ignore, and then watch the test harness bus for a specified amount of time. If a Fault is published on the bus during that time, the test fails. If the time elapses, the test passes.
// This event brings the state machine into the "Draft" state,
// so the event should be ignored while in Draft.
[Test]
public async Task When_AnswerSetAutoSaved_And_StateIsDraft_Should_Ignore()
{
var instance = await CreateInstanceInState(Machine.Draft);
var answerSetId = instance.CorrelationId;
var formPackageInstanceId = instance.FormPackageInstanceId;
await TestHarness.Bus.Publish(new AnswerSetAutosaved
{
AnswerSetId = answerSetId,
FormPackageInstanceId = formPackageInstanceId,
});
Assert.That(await SagaHarness.Consumed.Any<AnswerSetAutosaved>());
Assert.That(await TestHarness.Published.Any<Fault<AnswerSetAutosaved>>(NoSuchMessageTimeout), Is.False);
}
I inferred this approach from this github issue. It works well when the whole suite of tests is run, but when I run such tests one-by-one, they fail because the first test of a run takes way more time to warm up.
I would very much like not to set the NoSuchMessageTimeout
to a longer period, because it makes the whole suite take ages to complete when the tests are passing. Usually these tests complete in under 50ms, it's just the first one can take something like 3 seconds, and I don't want to make every Should_Ignore
test take this long by default.
I tried switching approach to making the state machine publish a generically typed message Ignored<T>
(where T
would be the ignored message) every time an event is ignored. Kind of how MassTransit's Fault<T>
message is published if an event is neither handled nor ignored.
public class Ignored<T> where T : class
{
}
I'm gonna listen for these Ignored<T>
events in the tests, and pass the test when the event is seen. That way, passing these tests will be the "fast path", since as soon as the ignored message is consumed by the state machine, it will let me know it was ignored. Failing the test would be the "slow path" since the test would wait for the NoSuchMessageTimeout
to elapse before failing. That is much better since unit tests spend most of their lifetime in a codebase passing.
But I cannot for the life of me figure out how to publish the events when messages are ignored. I tried specifying a callback using the OnUnhandledEvent
method like so:
OnUnhandledEvent(x => x.Publish<Ignored</*need the message type here*/>>(x.Message // this property doesn't exist
I'm not sure the OnUnhandledEvent is the right "hook" to use, or if this can even be done. I'm kind of stuck here and I can't find anyone else with my problem.