1

我正在尝试为 Automatonymous 状态机编写测试,但我在正确处理它时遇到了一些麻烦,而且我发现的文档很少。

这是我目前进行的一项测试:

[TestFixture]
public class MyProcessStateMachineTests
{
    InMemoryTestHarness _Harness;
    MyProcessStateMachine _Machine;
    StateMachineSagaTestHarness<MyProcess, MyProcessStateMachine> _Saga;

    [OneTimeSetUp]
    public void ConfigureMessages()
    {
        MessageCorrelation.UseCorrelationId<RequestMyDetails>(x => x.CorrelationId);
        MessageCorrelation.UseCorrelationId<FileAttached>(x => x.CorrelationId);
        MessageCorrelation.UseCorrelationId<PDFGenerated>(x => x.CorrelationId);
        MessageCorrelation.UseCorrelationId<CustomerAttachFile>(x => x.CorrelationId);
        MessageCorrelation.UseCorrelationId<AddCustomerNote>(x => x.CorrelationId);
        MessageCorrelation.UseCorrelationId<EmailPublished>(x => x.CorrelationId);
    }


    [SetUp]
    public void InitializeTestHarness()
    {
        _Harness = new InMemoryTestHarness();
        _Machine = new MyProcessStateMachine( /* snip */ );
        _Saga = _Harness.StateMachineSaga<MyProcess, MyProcessStateMachine>(_Machine);

        _Harness.Start().Wait();
    }

    [TearDown]
    public void StopTestHarness()
    {
        _Harness.Stop();
    }


    [Test]
    public async Task ShouldAttachToCustomer()
    {
        var sagaId = Guid.NewGuid();
        var custId = Guid.NewGuid();
        var fileAttached = BuildFileAttachedMessage(sagaId);

        await _Harness.InputQueueSendEndpoint.Send(BuildStartMessage(sagaId));
        await _Harness.InputQueueSendEndpoint.Send(BuildDetailsReceivedMessage(sagaId));
        await _Harness.InputQueueSendEndpoint.Send(BuildPdfGeneratedMessage(sagaId));
        await _Harness.InputQueueSendEndpoint.Send(fileAttached);

        // Next line is based on [the answer here][1]
        // Once the above messages are all consumed and processed,
        // the state machine should be in AwaitingEmail state
        await _Saga.Match(x =>
            x.CorrelationId == sagaId
                && x.CurrentState == _Machine.AwaitingEmail.Name,
            new TimeSpan(0, 0, 30));

        // Grab the instance and Assert stuff...
    }

    // Snip...
}

鉴于 _Saga.Match 调用找到了匹配项,我希望所有消息都已被处理,并且我应该能够获取我的状态机实例和发布的事件并检查它们的值 - 但事实并非如此。当我在夹具中运行测试时,有时我得到的实例已经消耗并发布了预期的消息;有时它还不完全在那里。

我尝试使用以下方法获取我的实例:

var inst = _Saga.Sagas.FirstOrDefault(x => x.Saga.CorrelationId == sagaId);

或通过以下方式获取已发布的事件:

var test = _Harness.Published
    .FirstOrDefault(x => x.MessageType == typeof(IAttachFile) && x.Context.CorrelationId == sagaId);

但是对 Match 的调用是否成功并不重要,状态机实例(和已发布的事件)并不总是存在。

我假设来自 Automatonymous、MassTransit 或测试工具的异步进程导致了不一致。有什么帮助吗?

使用 MassTransit、MassTransit.Automatonymous 和 MassTransit.TestFramework 5.1.2.1528、Automatonymous 4.1.1.102 进行测试,

编辑:

进一步审查,我发现当我遇到问题时,调用Match( ... ) 没有成功 - 它超时。(我一直错误地假设超时会引发异常。)

4

2 回答 2

0

如果这可能对其他人有帮助,这就是我最终让它工作的方式:

[TestFixture]
public class ProcessStateMachineTests : InMemoryTestFixture
{
    TimeSpan _TestTimeout = new TimeSpan(0, 1, 0);
    ProcessStateMachine _Machine;
    InMemorySagaRepository<Process> _Repository;


    protected override void ConfigureInMemoryReceiveEndpoint(
        IInMemoryReceiveEndpointConfigurator configurator)
    {
        _Machine = new ProcessStateMachine();
        _Repository = new InMemorySagaRepository<Process>();

        configurator.StateMachineSaga(_Machine, _Repository);
    }


    [OneTimeSetUp]
    public void ConfigureMessages()
    {
        // Message correlation and such happens in here
        ProcessStateMachine.ConfigureMessages();
    }

    [Test]
    public async Task OnInitializationIStartProcessIsConsumed()
    {
        var sagaId = Guid.NewGuid();
        var customerId = Guid.NewGuid();

        await SetupStateMachine(sagaId, customerId, _Machine.AwaitingDetails.Name);

        var msg = InMemoryTestHarness.Consumed
            .Select<IStartProcess>(x => x.Context.Message.RequestId == sagaId)
            .FirstOrDefault();

        // Assert against msg for expected results
    }

    [Test]
    public async Task OnStartProcessAddCustomerNoteAndRequestDetailsPublished()
    {
        var sagaId = Guid.NewGuid();
        var customerId = Guid.NewGuid();

        await SetupStateMachine(sagaId, customerId, _Machine.AwaitingDetails.Name);

        var pubdNoteAddedMsg = InMemoryTestHarness.Published
            .Select<IAddCustomerNote>()
            .FirstOrDefault(x => x.Context.Message.RequestId == sagaId);
        var pubdDetailsReqdMsg = InMemoryTestHarness.Published
            .Select<IRequestDetails>()
            .FirstOrDefault(x => x.Context.Message.RequestId == sagaId);

        Assert.IsTrue(pubdNoteAddedMsg != null);
        Assert.IsTrue(pubdDetailsReqdMsg != null);

        Assert.AreEqual(sagaId, pubdNoteAddedMsg.Context.CorrelationId);
        Assert.AreEqual(sagaId, pubdDetailsReqdMsg.Context.CorrelationId);

        Assert.AreEqual(customerId, pubdNoteAddedMsg.Context.Message.CustomerId);
        Assert.IsFalse(String.IsNullOrEmpty(pubdNoteAddedMsg.Context.Message.Note));
    }


    private async Task SetupStateMachine(
        Guid sagaId,
        Guid customerId,
        String toState)
    {
        if (String.IsNullOrEmpty(toState))
            return;

        await MoveStateMachineForward(BuildStartMessage(), x => x.AwaitingDetails);

        var awaitingDetailsId = await _Repository.ShouldContainSagaInState(
            sagaId, _Machine, x => x.AwaitingDetails, _TestTimeout);

        Assert.IsNotNull(awaitingDetailsId, "Error, expected state machine in AwaitingDetails state");

        if (toState == _Machine.AwaitingDetails.Name)
            return;

        // ...and more stuff to move to later states, depending on
        // where I want my test's starting point to be...


        async Task MoveStateMachineForward<T>(
            T message,
            Func<ProcessStateMachine, Automatonymous.State> targetState)
            where T : class
        {
            await InputQueueSendEndpoint.Send(message);

            var foundSagaId = await _Repository.ShouldContainSagaInState(
                sagaId, _Machine, targetState, _TestTimeout);

            Assert.IsTrue(foundSagaId.HasValue);
        }


        IStartProcess BuildStartMessage()
        {
            return new StartProcessMessage(sagaId, customerId);
        }
    }
}
于 2018-06-22T15:59:34.480 回答
0

对于遇到相同问题的任何人:您尝试获取实例的方式

var inst = _Saga.Sagas.FirstOrDefault(x => x.Saga.CorrelationId == sagaId);

可以这样修复

var instanceIds = await _sagaHarness.Match(
    instance => instance.CorrelationId == sagaId
        && instance.CurrentState == _machine.Working.Name,
        new TimeSpan(0, 0, 30));

将预期状态包含在条件中很重要。仅通过correlationid 获取实例然后测试CurrentState 可能会失败,因为设置状态(saga 执行是异步的)可能需要一些时间。请参阅https://github.com/balintn22/AutomatonymousTestExample的完整示例

于 2019-10-15T11:11:52.137 回答