使用在Task.Run中调用的Moq方法进行单元测试

时间:2017-11-29 20:28:13

标签: 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()=>等待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);
}

问题是永远不会调用该方法,因此我的断言失败。

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

3 个答案:

答案 0 :(得分:4)

您的代码是应用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);
}

答案 1 :(得分:1)

我提出的解决方案是提取新的服务metod,它是Sync,在其中我私下使用Thead.Run在一个单独的线程内调用我的异步方法。

public void ReportExceptionOnEmail(string recipient, string exceptionBody)
{
    Task.Run(async () => await this.SendEmailForThrownException(recipient, exceptionBody));
}

private async Task SendEmailForThrownException(string recipientEmail, string exceptionBody)

所以,现在我可以毫无问题地对我的 ReportExceptionOnEmail 方法进行单元测试。

答案 2 :(得分:0)

我尝试了上面方案的一个简单的简化版本,单元测试一直通过。

public interface IService
{
    Task<bool> Do();
}

public class AsyncService : IService
{
    public async Task<bool> Do()
    {
        return await Task.FromResult(true);
    }
}

public class MyClass
{
    private IService service;

    public MyClass(IService service)
    {
        this.service = service;
    }

    public async Task<bool> Run()
    {
        return await this.service.Do();
    }
}

[TestMethod]
public async Task TestAsyncMethod()
{
    Mock<IService> mockService = new Mock<IService>();
    mockService.Setup(m => m.Do()).Returns(Task.FromResult(false));

    MyClass myClass = new MyClass(mockService.Object);
    await myClass.Run();

    mockService.Verify(m => m.Do(), Times.Once);
}

看起来你需要让OnActionException返回一个Task&lt; string&gt;然后使单元测试异步。因此,不要使用Task.Run(),只需在OnActionException方法中返回await this._mailService.SendEmailForThrownException()。