c# - 使用在 Task.Run 内部调用的 Moq 方法进行单元测试

标签 c# unit-testing mocking moq assert

我正在尝试在要测试的方法中模拟服务调用。

方法体如下所示:

public string OnActionException(HttpActionContext httpRequest, Exception ex)
{
    var formattedActionException = ActionLevelExceptionManager.GetActionExceptionMessage(httpRequest);

    var mainErrorMessage = $"[{formattedActionException.ErrorId}]{formattedActionException.ErrorMessage}, {ex.Message}";

    this.LogError(mainErrorMessage, ex);

    if (this._configuration.MailSupportOnException)
        Task.Run(async () => await this._mailService.SendEmailForThrownException(this._configuration.SupportEmail, $"{mainErrorMessage} ---> Stack trace: {ex.StackTrace.ToString()}")); 

    return $"(ErrID:{formattedActionException.ErrorId}) {formattedActionException.ErrorMessage} {formattedActionException.KindMessage}";
}

我想在测试中模拟的是:

Task.Run(async () => await this._mailService.SendEmailForThrownException(this._configuration.SupportEmail, $"{mainErrorMessage} ---> 堆栈跟踪:{ex.StackTrace.ToString()}"));

测试方法如下:

[TestMethod]
public void We_Send_System_Exception_On_Email_If_Configured_In_Settings()
{
    // arrange
    this._configurationWrapperMock.Setup(cwm => cwm.MailSupportOnException)
        .Returns(true);
    this._mailServiceMock.Setup(msm => msm.SendEmailForThrownException(It.IsAny<string>(), It.IsAny<string>()))
        .Returns(Task.FromResult(0));

    // act
    var logger = new ApiLogger(this._configurationWrapperMock.Object, this._mailServiceMock.Object);
    logger.OnActionException(
        new HttpActionContext(
            new HttpControllerContext()
            {
                Request = new HttpRequestMessage()
                {
                    Method = HttpMethod.Get,
                    RequestUri = new System.Uri("https://www.google.bg/")
                }
            }, 
            new ReflectedHttpActionDescriptor() { }
        ), 
        new System.Exception());

    // assert
    this._mailServiceMock.Verify(
        msm => msm.SendEmailForThrownException(It.IsAny<string>(), It.IsAny<string>()), 
        Times.Once);
}

问题是该方法从未被调用,所以我的断言失败了。

编辑:我可以将我的问题改为:我需要如何重写我的方法以使其可测试?

最佳答案

你的代码是经典情况下的应用Humble Object Pattern .

在这种情况下,您所要做的就是将 Task.Run 提取到一个虚拟方法中,然后部分模拟 SUT 并覆盖该虚拟方法:

public class ApiLogger
{
    ...

    public string OnActionException(Exception ex)
    {
        ...
        if (this._configuration.MailSupportOnException)
            RunInTask(...);
        ...
    }

    public virtual Task RunInTask(Action action)
    {
        return Task.Run(action);
    }
}

然后测试看起来像:

[TestMethod]
public void We_Send_System_Exception_On_Email_If_Configured_In_Settings()
{
    ...

    var logger = new Mock<ApiLogger>(MockBehavior.Default, 
                                     new object[]
                                     {
                                       this._configurationWrapperMock.Object, 
                                       this._mailServiceMock.Object
                                     }).Object;

    logger.OnActionException(...);


    this._mailServiceMock.Verify(
            msm => msm.SendEmailForThrownException(It.IsAny<string>(), It.IsAny<string>()), 
            Times.Once);
}

关于c# - 使用在 Task.Run 内部调用的 Moq 方法进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47561264/

相关文章:

android - 无法解析符号 InstantTaskExecutorRule

模拟消息队列

c# - 在 C# 中的应用程序之间传递信息

c# - 如何在C#中将对象与null进行比较

c# - 如何从 View 中获取在文本框中输入的值

android - Robolectric:如何设置希望 Calendar 类返回的时间?

testing - Angular 2 测试 - process.env

c# - 如何仅使用一个 for 循环反转多个列表框中的选定项目?

c# - Jon Skeet 的 "TimeMachine"异步单元测试框架是否需要 ManuallyPumpedSynchronizationContext?

java - 即使没有单元测试,构建自动化真的有助于提高生产力吗?