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?
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.