如何让Moq验证具有out参数的方法

Dar*_*Guy 5 c# nunit unit-testing moq

我有一个接口定义,其中方法定义了out参数

public interface IRestCommunicationService
{
    TResult PerformPost<TResult, TData>(string url, TData dataToSend, out StandardErrorResult errors);
}
Run Code Online (Sandbox Code Playgroud)

我有下面的类正在使用上述接口

    public TripCreateDispatchService(IRestCommunicationAuthService restCommunicationService, ISettings settings)
    {
        _restCommunicationService = restCommunicationService;
        _settings = settings;
    }

    public FlightAlert CreateTrip(string consumerNumber, PostAlertModel tripModel, out StandardErrorResult apiErrors)
    {
        url = .. code ommited
        var result = _restCommunicationService.PerformPost<FlightAlert, PostAlertModel>(url), tripModel, out apiErrors);
        return result;
    }
Run Code Online (Sandbox Code Playgroud)

在我的单元测试中,我试图验证是否调用了RestCommunication对象的PerformPost方法。

但是无论我做什么,我都无法让Moq验证该方法是否已被调用

    public void DispatchService_PerformPost()
    {
        var consumerNumber = ...
        var trip = ...
        var result = ...
        var apiErrors = new StandardErrorResult();
        ... code to setup mock data
        _mockRestCommunicationService = new  Mock<IRestCommunicationAuthService>();
        _mockEestCommunicationService.Setup(x => x.PerformPost<string, PostAlertModel>(It.IsAny<string>(), It.IsAny<PostAlertModel>(), out apiErrors)).Verifiable();


        _systemUnderTest.CreateTrip(consumerNumber, trip, out apiErrors);

        _mockRestCommunicationService.Verify(m => 
            m.PerformPost<StandardErrorResult, PostAlertModel>(
            It.IsAny<string>(), 
            It.IsAny<PostAlertModel>(), out apiErrors
            ), Times.Once);
    }
Run Code Online (Sandbox Code Playgroud)

但是我收到以下错误

Moq.MockException : 

Expected invocation on the mock once, but was 0 times: 
m => m.PerformPost<StandardErrorResult,PostAlertModel>(It.IsAny<String>(), It.IsAny<PostAlertModel>(), .apiErrors)

No setups configured.
Run Code Online (Sandbox Code Playgroud)

我该如何验证该方法已被调用。

我正在使用Moq和NUnit

更新1

根据Sunny的评论,我已修改测试以使用回调,如下所示

var consumerNumber = ...
var trip = ...
var result = ...
StandardErrorResult apiErrors;
_mockRestCommunicationService.Setup(x => x.PerformPost<string, PostAlertModel>(
            It.IsAny<string>(), It.IsAny<PostAlertModel>(), out apiErrors))
    .Callback<string, PostAlertModel, StandardErrorResult>
    ((s, m, e) => e.Errors = new System.Collections.Generic.List<StandardError>()
    {
        new StandardError { ErrorCode = "Code", ErrorMessage = "Message" }
    });

_systemUnderTest.CreateTrip(consumerNumber, trip, out apiErrors);
Assert.That(apiErrors.Errors, Is.Not.Null);
Run Code Online (Sandbox Code Playgroud)

这是执行测试时现在抛出的错误。

System.ArgumentException : Invalid callback. Setup on method with 
parameters (String,PostAlertModel,StandardErrorResult&) 
cannot invoke callback with parameters (String,PostAlertModel,StandardErrorResult).
   at Moq.MethodCall.ThrowParameterMismatch(ParameterInfo[] expected, ParameterInfo[] actual)
   at Moq.MethodCall.SetCallbackWithArguments(Delegate callback)
   at Moq.MethodCallReturn`2.Callback(Action`3 callback)
Run Code Online (Sandbox Code Playgroud)

在安装程序语句中引发此错误。

仅供参考。我正在使用Resharper 8,并使用他们的测试运行程序来执行测试。

如果我尝试将out参数添加到回调中,则代码将无法编译。

如果我将设置修改为

_mockRestCommunicationService.Setup(x => x.PerformPost<string, PostAlertModel>(
It.IsAny<string>(), It.IsAny<PostAlertModel>(), out apiErrors))
.Callback
    ((string s, PostAlertModel m, StandardErrorResult e) => e.Errors = new System.Collections.Generic.List<StandardError>()
        {
            new StandardError { ErrorCode = "Code", ErrorMessage = "Message" }
        });
Run Code Online (Sandbox Code Playgroud)

pek*_*aaw 9

在我正在进行的项目中,我们使用了out It.Ref<T>.IsAny其中 T 是输出参数类型(Moq 版本 4.14)。这是因为out被用作参数修饰符,使得​​值通过引用而不是通过值传递

在您的情况下,验证方法可能如下所示:

_mockRestCommunicationService.Verify(
        _ => _.PerformPost<StandardErrorResult, PostAlertModel>(
            It.IsAny<string>(), 
            It.IsAny<PostAlertModel>(),
            out It.Ref<StandardErrorResult>.IsAny
        ),
        Times.Once
    );
Run Code Online (Sandbox Code Playgroud)

为了确保没有其他意外调用发生,您还可以添加:

_mockRestCommunicationService.VerifyNoOtherCalls()
Run Code Online (Sandbox Code Playgroud)


Sun*_*nov 4

最好实际使用AAA,而不是验证模拟。设置您的方法以返回某些特定结果并断言该结果已返回:

var myCommResult = new PostAlertModel();
_mockEestCommunicationService
    .Setup(x => x.PerformPost<string, PostAlertModel>(It.IsAny<string>(), It.IsAny<PostAlertModel>(), out apiErrors)
    .Returns(myCommResult);

var response = _systemUnderTest.CreateTrip(consumerNumber, trip, out apiErrors);

Assert.AreSame(myCommResult, response);
Run Code Online (Sandbox Code Playgroud)

上面将根据示例代码验证该方法是否被调用。

如果由于某种原因,问题中的代码并不能真正代表真实的代码,并且无法在方法的返回上断言,那么您可以使用 Callback 代替,并在错误中添加一些内容以便您可以验证。

就像是:

_mockEestCommunicationService
   .Setup(x => x.PerformPost<string, PostAlertModel>(It.IsAny<string>(), It.IsAny<PostAlertModel>(), out apiErrors))
   .Callback( (string s, PostAlertModel m, StandardErrorResult e) => e.Add("Some error to test");
Run Code Online (Sandbox Code Playgroud)

然后验证 apiErrors 是否包含您在回调中插入的错误。