How to verify log message in Unit testing for a passing test?
Asked Answered
Q

2

10

I'm testing an endpoint. I need to figure out how to get the test to pass with my loggerMock. Here is how I currently have the test set up:

public void GetExceptionReportSessionData_Returns200OK()
        {
            //Arrange 
            var response = new RetrieveExceptionReportSessionDatesResponse
            {
                RetrieveExceptionReportSessionDatesResult = string.Empty
            };
            var serviceClient = new Mock<WorkflowService.WorkflowService>();      
            serviceClient
                .Setup(x => x.RetrieveExceptionReportSessionDatesAsync(It.IsAny<RetrieveExceptionReportSessionDatesRequest>()))
               .ReturnsAsync(response);

            var loggerMock = new Mock<ILogger>();
            loggerMock.Setup(x => x.LogInfo(null));

            var controller = new ExceptionReportController(loggerMock.Object);

            var ctx = new ControllerContext() { HttpContext = new DefaultHttpContext() };
            ctx.HttpContext.Request.Headers["token"] = "fake_token_here"; //Set header
            controller.ControllerContext = ctx;

            //Act
            var result = controller.GetExceptionReportSessionData();

            //Assert
            var viewResult = Assert.IsType<OkObjectResult>(result);
            Assert.Equal(StatusCodes.Status200OK, viewResult.StatusCode);


        }

Here is how the logger is setup in the endpoint when returning a 200:

if (result != null && result.ExceptionReportLines != null && result.ExceptionReportLines.Count > 0)
            {
                logText = LogFormatter.Format(
                                WebUtilities.GetUser((ClaimsIdentity)HttpContext.User.Identity),
                                startTime, DateTime.Now, Privilege.ViewOrderExceptionReport,
                                "Get Exception Report", "Exception Report retrieved successfully.");
                logger.LogInfo(logText);
            }
            else
            {
                logText = LogFormatter.Format
                                (WebUtilities.GetUser((ClaimsIdentity)HttpContext.User.Identity),
                                startTime, DateTime.Now, Privilege.ViewOrderExceptionReport,
                                "Get Exception Report", "Exception report is empty for the given report filters.");
                logger.LogWarn(logText);
            }

            return Ok(result);

My test is set up so that the latter message appears. How can I get the test to pass?

Quach answered 29/5, 2020 at 17:2 Comment(1)
Out of curiousity, where are LogInfo and LogWarn coming from? They are not MS extensions...Varden
P
3

Instead of passing just a null value to Setup of LogInfo method you can use an expression to match the logText string

loggerMock
    .Setup(x => x.LogInfo(It.Is<string>(s => s.Contains("Exception Report retrieved successfully."))))
    .Verifiable();

And use Verify() in Assert step

loggerMock.Verify();

It ensures, that LogInfo() method in loggerMock was called with string matches the specified expression. Have a look at matching arguments in Moq wiki for more details

Portly answered 29/5, 2020 at 17:8 Comment(1)
ILogger doesn't have a LogInfo method it has a LogInformation method and it is a static extension method so it cannot be used with Moq. I'm not sure how this ever worked.Stavros
M
23

If you are using LogError, LogDebug, LogWarning etc methods in .NET those methods are extension methods and now the issue is that you can't mock an extension method. So what you need to do is mock the underlying method which is actually called when you call LogError, LogDebug, LogWarning etc methods.

Actually, all of those methods calls Log method so you need to mock Log method.

Definition of Log method as below

  void Log (this ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, string message, params object[] args)

You can mock Log method with LogWarning extension method as below

[Fact]
public void VerifyLogWarning()
{
    // Arrange
    var loggerMock = new Mock<ILogger<MyClass>>();
    var myclass = new MyClass(loggerMock.Object);

    // Act
    myclass.TestMethod();

    // Assert
    loggerMock.Verify(
        x => x.Log(
            It.Is<LogLevel>(l => l == LogLevel.Warning),
            It.IsAny<EventId>(),
            It.Is<It.IsAnyType>((v, t) => true),
            It.IsAny<Exception>(),
            It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => true)), Times.Once);
}

In the above code, we are verifying whether we have called LogWarning once as a part of TestMethod() call.

You can also verify log warning message as below

 // Assert
    loggerMock.Verify(
        x => x.Log(
            It.Is<LogLevel>(l => l == LogLevel.Warning),
            It.IsAny<EventId>(),
            It.Is<It.IsAnyType>((v, t) =>  v.ToString() == "LogWarning Message......"),
            It.IsAny<Exception>(),
            It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => true)), Times.Once);
Maggio answered 10/8, 2021 at 2:12 Comment(2)
This was the real answer to what I was looking for. The "It.IsAnyType" was something I didn't know about :)Dominations
Getting through multiple extension methods to the base method was the key!Manassas
P
3

Instead of passing just a null value to Setup of LogInfo method you can use an expression to match the logText string

loggerMock
    .Setup(x => x.LogInfo(It.Is<string>(s => s.Contains("Exception Report retrieved successfully."))))
    .Verifiable();

And use Verify() in Assert step

loggerMock.Verify();

It ensures, that LogInfo() method in loggerMock was called with string matches the specified expression. Have a look at matching arguments in Moq wiki for more details

Portly answered 29/5, 2020 at 17:8 Comment(1)
ILogger doesn't have a LogInfo method it has a LogInformation method and it is a static extension method so it cannot be used with Moq. I'm not sure how this ever worked.Stavros

© 2022 - 2024 — McMap. All rights reserved.