C# 单元测试利用异步调用的MassTransit消费者

C# 单元测试利用异步调用的MassTransit消费者,c#,asynchronous,async-await,restsharp,masstransit,C#,Asynchronous,Async Await,Restsharp,Masstransit,我们将MassTransit异步消息传递(在RabbitMQ之上)用于我们的微服务体系结构 我们在测试消费者时遇到了问题,而消费者又会进行异步调用 下面的示例显示了一个简单的MassTransit消费者,它使用RestSharp进行出站调用,并使用ExecuteAsync异步方法 public class VerifyPhoneNumberConsumer : Consumes<VerifyPhoneNumber>.Context { IRestClient _restCli

我们将MassTransit异步消息传递(在RabbitMQ之上)用于我们的微服务体系结构

我们在测试消费者时遇到了问题,而消费者又会进行异步调用

下面的示例显示了一个简单的MassTransit消费者,它使用RestSharp进行出站调用,并使用ExecuteAsync异步方法

public class VerifyPhoneNumberConsumer : Consumes<VerifyPhoneNumber>.Context
{
    IRestClient _restClient;
    RestRequest _request;
    PhoneNumber _phoneNumber;
    PhoneNumberVerificationResponse _responseData;

    public VerifyPhoneNumberConsumer(IRestClient client)
    {
        _restClient = client;
    }

    public void Consume(IConsumeContext<VerifyPhoneNumber> context)
    {
        try
        {
            //we can do some standard message verification/validation here 

            _restClient.ExecuteAsync<PhoneNumberVerificationResponse>(_request, (response) =>
            {
                //here we might do some standard response verification

                _responseData = response.Data;

                _phoneNumber = new PhoneNumber()
                {
                    Number = _responseData.PhoneNumber
                };

                context.Respond(new VerifyPhoneNumberSucceeded(context.Message)
                {
                    PhoneNumber = _phoneNumber
                });
            });
        }
        catch (Exception exception)
        {
            context.Respond(new VerifyPhoneNumberFailed(context.Message)
            {
                PhoneNumber = context.Message.PhoneNumber,
                Message = exception.Message
            });
        }
    }
}
公共类VerifyPhoneNumberConsumer:Consumes.Context
{
IRestClient(IRestClient);
请求重新请求;
电话号码(PhoneNumber);;
PhoneNumberFicationResponse_responseData;
公共验证电话号码消费者(IRestClient客户端)
{
_restClient=client;
}
公共void消费(IConsumeContext上下文)
{
尝试
{
//我们可以在这里进行一些标准消息验证
_restClient.ExecuteAsync(_请求,(响应)=>
{
//这里我们可以做一些标准响应验证
_responseData=response.Data;
_phoneNumber=新的phoneNumber()
{
Number=_responseData.PhoneNumber
};
context.Respond(新VerifyPhoneNumberSucceed(context.Message)
{
PhoneNumber=\u PhoneNumber
});
});
}
捕获(异常)
{
context.Respond(新VerifyPhoneNumberFailed(context.Message)
{
PhoneNumber=context.Message.PhoneNumber,
Message=异常。Message
});
}
}
}
此示例的单元测试可能如下所示:

[TestFixture]
public class VerifyPhoneNumberConsumerTests
{
    private VerifyPhoneNumberConsumer _consumer;
    private PhoneNumber _phoneNumber;
    private RestResponse _response;
    private VerifyPhoneNumber _command;

    private AutoResetEvent _continuationEvent;
    private const int CONTINUE_WAIT_TIME = 1000;

    [SetUp]
    public void Initialize()
    {
        _continuationEvent = new AutoResetEvent(false);
        _mockRestClient = new Mock<IRestClient>();
        _consumer = new VerifyPhoneNumberConsumer(_mockRestClient.Object);
        _response = new RestResponse();
        _response.Content = "Response Test Content";
        _phoneNumber = new PhoneNumber()
        {
            Number = "123456789"
        };
        _command = new VerifyPhoneNumber(_phoneNumber);
    }

    [Test]
    public  void VerifyPhoneNumber_Succeeded()
    {
        var test = TestFactory.ForConsumer<VerifyPhoneNumberConsumer>().New(x =>
        {
            x.ConstructUsing(() => _consumer);
            x.Send(_command, (scenario, context) => context.SendResponseTo(scenario.Bus));
        });

        _mockRestClient.Setup(
            c =>
            c.ExecuteAsync(Moq.It.IsAny<IRestRequest>(),
                                                            Moq.It
                                                               .IsAny<Action<IRestResponse<PhoneNumberVerificationResponse>, RestRequestAsyncHandle>>()))
                                                               .Callback<IRestRequest, Action<IRestResponse<PhoneNumberVerificationResponse>, RestRequestAsyncHandle>>((
                                                                   request, callback) =>
                                                               {
                                                                   var responseMock = new Mock<IRestResponse<PhoneNumberVerificationResponse>>();
                                                                   responseMock.Setup(r => r.Data).Returns(GetSuccessfulVericationResponse());
                                                                   callback(responseMock.Object, null);
                                                                   _continuationEvent.Set();
                                                               });


        test.Execute();

        _continuationEvent.WaitOne(CONTINUE_WAIT_TIME);

        Assert.IsTrue(test.Sent.Any<VerifyPhoneNumberSucceeded>());
    }

    private PhoneNumberVerificationResponse GetSuccessfulVericationResponse()
    {
        return new PhoneNumberVerificationResponse
            {
                PhoneNumber = _phoneNumber
            };
    }
}
[Test]
public void VerifyPhoneNumber_Succeeded()
{
    var test = TestFactory.ForConsumer<VerifyPhoneNumberConsumer>().New(x =>
    {
        x.ConstructUsing(() => _consumer);
        x.Send(_command, (scenario, context) => context.SendResponseTo(scenario.Bus));
    });

    var response = (IRestResponse<PhoneNumberVerificationResponse>)new RestResponse<PhoneNumberVerificationResponse>();
    response.Data = GetSuccessfulVericationResponse();

    var taskResponse = Task.FromResult(response);
    Expect.MethodCall(
        () => _client.ExecuteGetTaskAsync<PhoneNumberVerificationResponse>(Any<IRestRequest>.Value.AsInterface))
          .Returns(taskResponse);

    test.Execute();

    Assert.IsTrue(test.Sent.Any<VerifyPhoneNumberSucceeded>());
}
[TestFixture]
公共类VerifyPhoneNumber消费测试
{
私人VerifyPhoneNumberConsumer\u消费者;
私人电话号码(电话号码);;
私人回复(u)回复;;
私有VerifyPhoneNumber_命令;
私人自动存储事件_continuationEvent;
私有常量int CONTINUE\u WAIT\u TIME=1000;
[设置]
公共无效初始化()
{
_continuationEvent=新的自动恢复事件(false);
_mockRestClient=newmock();
_消费者=新的VerifyPhoneNumberConsumer(\u mockRestClient.Object);
_响应=新的重新响应();
_response.Content=“响应测试内容”;
_phoneNumber=新的phoneNumber()
{
Number=“123456789”
};
_命令=新的VerifyPhoneNumber(\u phoneNumber);
}
[测试]
public void VerifyPhoneNumber_successed()
{
var test=TestFactory.ForConsumer().New(x=>
{
x、 构造使用(()=>\u消费者);
x、 Send(_命令,(scenario,context)=>context.SendResponseTo(scenario.Bus));
});
_mockRestClient.Setup(
c=>
c、 ExecuteAsync(Moq.It.IsAny(),
最小起订量

.IsAny

老实说,异步方法的复杂性是MassTransit 3的关键驱动因素之一。虽然它还没有准备好,但它使消费者的异步方法调用变得更好

在上面的测试中,由于您正在REST客户端上调用ExecuteAsync(),并且没有在使用者中等待响应(使用.Result或.Wait),HTTP调用将在消息使用者返回后继续。因此,这可能是问题的一部分

在MT3中,该消费者将被写为:

public async Task Consume(ConsumeContext<VerifyPhoneNumber> context)
{
    try
    {
        var response = await _restClient
            .ExecuteAsync<PhoneNumberVerificationResponse>(_request);
        var phoneNumber = new PhoneNumber()
        {
            Number = response.PhoneNumber
        };

        await context.RespondAsync(new VerifyPhoneNumberSucceeded(context.Message)
        {
            PhoneNumber = _phoneNumber
        });
    }
    catch (Exception exception)
    {
        context.Respond(new VerifyPhoneNumberFailed(context.Message)
        {
            PhoneNumber = context.Message.PhoneNumber,
            Message = exception.Message
        });
    }        
}
公共异步任务使用(consumercontext)
{
尝试
{
var response=wait\u restClient
.ExecuteAsync(_请求);
var phoneNumber=新电话号码()
{
Number=response.PhoneNumber
};
wait context.RespondAsync(新VerifyPhoneNumberSucceed(context.Message)
{
PhoneNumber=\u PhoneNumber
});
}
捕获(异常)
{
context.Respond(新VerifyPhoneNumberFailed(context.Message)
{
PhoneNumber=context.Message.PhoneNumber,
Message=异常。Message
});
}        
}

我想出了下面的解决方案,看起来更优雅、更恰当。如果我的假设有误,请随时纠正我

我在我的消费者中修改了RestSharp执行,因此我的消费者如下所示:

[TestFixture]
public class VerifyPhoneNumberConsumerTests
{
    private VerifyPhoneNumberConsumer _consumer;
    private PhoneNumber _phoneNumber;
    private RestResponse _response;
    private VerifyPhoneNumber _command;

    private AutoResetEvent _continuationEvent;
    private const int CONTINUE_WAIT_TIME = 1000;

    [SetUp]
    public void Initialize()
    {
        _continuationEvent = new AutoResetEvent(false);
        _mockRestClient = new Mock<IRestClient>();
        _consumer = new VerifyPhoneNumberConsumer(_mockRestClient.Object);
        _response = new RestResponse();
        _response.Content = "Response Test Content";
        _phoneNumber = new PhoneNumber()
        {
            Number = "123456789"
        };
        _command = new VerifyPhoneNumber(_phoneNumber);
    }

    [Test]
    public  void VerifyPhoneNumber_Succeeded()
    {
        var test = TestFactory.ForConsumer<VerifyPhoneNumberConsumer>().New(x =>
        {
            x.ConstructUsing(() => _consumer);
            x.Send(_command, (scenario, context) => context.SendResponseTo(scenario.Bus));
        });

        _mockRestClient.Setup(
            c =>
            c.ExecuteAsync(Moq.It.IsAny<IRestRequest>(),
                                                            Moq.It
                                                               .IsAny<Action<IRestResponse<PhoneNumberVerificationResponse>, RestRequestAsyncHandle>>()))
                                                               .Callback<IRestRequest, Action<IRestResponse<PhoneNumberVerificationResponse>, RestRequestAsyncHandle>>((
                                                                   request, callback) =>
                                                               {
                                                                   var responseMock = new Mock<IRestResponse<PhoneNumberVerificationResponse>>();
                                                                   responseMock.Setup(r => r.Data).Returns(GetSuccessfulVericationResponse());
                                                                   callback(responseMock.Object, null);
                                                                   _continuationEvent.Set();
                                                               });


        test.Execute();

        _continuationEvent.WaitOne(CONTINUE_WAIT_TIME);

        Assert.IsTrue(test.Sent.Any<VerifyPhoneNumberSucceeded>());
    }

    private PhoneNumberVerificationResponse GetSuccessfulVericationResponse()
    {
        return new PhoneNumberVerificationResponse
            {
                PhoneNumber = _phoneNumber
            };
    }
}
[Test]
public void VerifyPhoneNumber_Succeeded()
{
    var test = TestFactory.ForConsumer<VerifyPhoneNumberConsumer>().New(x =>
    {
        x.ConstructUsing(() => _consumer);
        x.Send(_command, (scenario, context) => context.SendResponseTo(scenario.Bus));
    });

    var response = (IRestResponse<PhoneNumberVerificationResponse>)new RestResponse<PhoneNumberVerificationResponse>();
    response.Data = GetSuccessfulVericationResponse();

    var taskResponse = Task.FromResult(response);
    Expect.MethodCall(
        () => _client.ExecuteGetTaskAsync<PhoneNumberVerificationResponse>(Any<IRestRequest>.Value.AsInterface))
          .Returns(taskResponse);

    test.Execute();

    Assert.IsTrue(test.Sent.Any<VerifyPhoneNumberSucceeded>());
}
公共类VerifyPhoneNumberConsumer:Consumes.Context { IRestClient(IRestClient); 请求重新请求; 电话号码(PhoneNumber);; PhoneNumberFicationResponse_responseData

public VerifyPhoneNumberConsumer(IRestClient client)
{
    _restClient = client;
}

public void Consume(IConsumeContext<VerifyPhoneNumber> context)
{
    try
    {
        //we can do some standard message verification/validation here 

        var response = await _restClient.ExecuteGetTaskAsync<PhoneNumberVerificationResponse>(_request);

        _responseData = response.Data;

        _phoneNumber = new PhoneNumber()
        {
            Number = _responseData.PhoneNumber
        };
    }
    catch (Exception exception)
    {
        context.Respond(new VerifyPhoneNumberFailed(context.Message)
        {
            PhoneNumber = context.Message.PhoneNumber,
            Message = exception.Message
        });
    }
}
公共验证电话号码消费者(IRestClient)
{
_restClient=client;
}
公共void消费(IConsumeContext上下文)
{
尝试
{
//我们可以在这里进行一些标准消息验证
var response=wait _restClient.ExecuteGetTaskAsync(_请求);
_responseData=response.Data;
_phoneNumber=新的phoneNumber()
{
Number=_responseData.PhoneNumber
};
}
捕获(异常)
{
context.Respond(新VerifyPhoneNumberFailed(context.Message)
{
PhoneNumber=context.Message.PhoneNumber,
Message=异常。Message
});
}
}
}

这利用了RestSharp的TPL异步功能,因此我不必自己做

因此,我可以将测试代码更改为以下内容:

[TestFixture]
public class VerifyPhoneNumberConsumerTests
{
    private VerifyPhoneNumberConsumer _consumer;
    private PhoneNumber _phoneNumber;
    private RestResponse _response;
    private VerifyPhoneNumber _command;

    private AutoResetEvent _continuationEvent;
    private const int CONTINUE_WAIT_TIME = 1000;

    [SetUp]
    public void Initialize()
    {
        _continuationEvent = new AutoResetEvent(false);
        _mockRestClient = new Mock<IRestClient>();
        _consumer = new VerifyPhoneNumberConsumer(_mockRestClient.Object);
        _response = new RestResponse();
        _response.Content = "Response Test Content";
        _phoneNumber = new PhoneNumber()
        {
            Number = "123456789"
        };
        _command = new VerifyPhoneNumber(_phoneNumber);
    }

    [Test]
    public  void VerifyPhoneNumber_Succeeded()
    {
        var test = TestFactory.ForConsumer<VerifyPhoneNumberConsumer>().New(x =>
        {
            x.ConstructUsing(() => _consumer);
            x.Send(_command, (scenario, context) => context.SendResponseTo(scenario.Bus));
        });

        _mockRestClient.Setup(
            c =>
            c.ExecuteAsync(Moq.It.IsAny<IRestRequest>(),
                                                            Moq.It
                                                               .IsAny<Action<IRestResponse<PhoneNumberVerificationResponse>, RestRequestAsyncHandle>>()))
                                                               .Callback<IRestRequest, Action<IRestResponse<PhoneNumberVerificationResponse>, RestRequestAsyncHandle>>((
                                                                   request, callback) =>
                                                               {
                                                                   var responseMock = new Mock<IRestResponse<PhoneNumberVerificationResponse>>();
                                                                   responseMock.Setup(r => r.Data).Returns(GetSuccessfulVericationResponse());
                                                                   callback(responseMock.Object, null);
                                                                   _continuationEvent.Set();
                                                               });


        test.Execute();

        _continuationEvent.WaitOne(CONTINUE_WAIT_TIME);

        Assert.IsTrue(test.Sent.Any<VerifyPhoneNumberSucceeded>());
    }

    private PhoneNumberVerificationResponse GetSuccessfulVericationResponse()
    {
        return new PhoneNumberVerificationResponse
            {
                PhoneNumber = _phoneNumber
            };
    }
}
[Test]
public void VerifyPhoneNumber_Succeeded()
{
    var test = TestFactory.ForConsumer<VerifyPhoneNumberConsumer>().New(x =>
    {
        x.ConstructUsing(() => _consumer);
        x.Send(_command, (scenario, context) => context.SendResponseTo(scenario.Bus));
    });

    var response = (IRestResponse<PhoneNumberVerificationResponse>)new RestResponse<PhoneNumberVerificationResponse>();
    response.Data = GetSuccessfulVericationResponse();

    var taskResponse = Task.FromResult(response);
    Expect.MethodCall(
        () => _client.ExecuteGetTaskAsync<PhoneNumberVerificationResponse>(Any<IRestRequest>.Value.AsInterface))
          .Returns(taskResponse);

    test.Execute();

    Assert.IsTrue(test.Sent.Any<VerifyPhoneNumberSucceeded>());
}
[测试]
public void VerifyPhoneNumber_successed()
{
var test=TestFactory.ForConsumer().New(x=>
{
x、 构造使用(()=>\u消费者);
x、 Send(_命令,(scenario,context)=>context.SendResponseTo(scenario.Bus));
});
var response=(IRestResponse)new response();
response.Data=GetSuccessfulVericationResponse();
var taskResponse=Task.FromResult(响应);
期待,方法