简体   繁体   English

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

[英]Unit testing with Moq method invoked inside of Task.Run

I am trying to mock a service call inside of a method that I want to test. 我试图在我要测试的方法中模拟服务调用。

The method body looks like this: 方法主体如下所示:

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}";
}

What I am trying to mock inside my test is: 我想在测试中模拟的是:

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

The test method looks like this: 测试方法如下:

[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);
}

The problem is that the method is never invoked, so my assert fails. 问题在于该方法从未被调用,所以我的断言失败了。

EDIT: I could change my question to: How do I need to re-write my method in order to make it testable? 编辑:我可以将我的问题更改为:我该如何重新编写我的方法以使其可测试?

Your code is a classic situation to apply Humble Object Pattern . 您的代码是应用Humble Object Pattern的典型情况。

In this case all you have to do is to extract Task.Run into a virtual method, then partial mock the SUT and override this virtual method: 在这种情况下,您要做的就是将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);
    }
}

Then the test will look like: 然后测试将如下所示:

[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);
}

The solution that I came up with was to extract new service metod which is Sync and inside of it I privately call my async method inside of a separate thread using Thead.Run. 我想出的解决方案是提取新的服务方法,即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)

So, now I can unit test my ReportExceptionOnEmail method with no problem. 因此,现在我可以毫无问题地对我的ReportExceptionOnEmail方法进行单元测试了。

I tried a simple, cut down version of your scenario above and the unit test consistently passes. 我在上面尝试了一个简单的简化版本,单元测试始终通过。

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);
}

It looks like you will need to make OnActionException return a Task<string> and then make the unit test asynchronous also. 看来您需要使OnActionException返回Task <string>,然后使单元测试也异步。 So rather than using Task.Run(), just return the await this._mailService.SendEmailForThrownException() in the OnActionException method. 因此,不要使用Task.Run(),而只需在OnActionException方法中返回等待this._mailService.SendEmailForThrownException()。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM