Search code examples
c#unit-testingmockingmoqassert

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

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?


Solution

  • 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. So rather than using Task.Run(), just return the await this._mailService.SendEmailForThrownException() in the OnActionException method.