简体   繁体   中英

Mocking and testing the LogError message using Moq and xUnit

I have a class level ILogger which is set up with the ILoggerFactory in the constructor. The logger is then used within a method within that class and this works perfectly.

I am struggling on how to Mock the ILogger and ILoggerFactory so I can unit test the LogError message. Can anyone show me an example of this?

I am using xUnit and Microsoft.Extentions.Logging for the loggin

//This is my unit test project
[Fact]
public void TestLogErrorMessage()
{
MyClass myClass = new MyClass  (MockLoggerFactory().Object);

    var result = myClass.Mymethod("a")

    //How do I test the LogError message???
}


//Not sure if this is correct
private Mock<ILoggerFactory> MockLoggerFactory()
{
           Mock<ILoggerFactory> mockLoggerFactory = new 
Mock<ILoggerFactory>(MockBehavior.Default);
    mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>()))
                           .Returns(MockLogger().Object);

        return mockLoggerFactory;
}

private Mock<ILogger> MockLogger()
{        
       var logger = new Mock<ILogger>();
       return logger;
}

//This is the class/method i need to test
private readonly ILogger logger;

public MyClass(ILoggerFactory loggerFactory)
{
       if (loggerFactory != null)
       {
           this.logger = loggerFactory.CreateLogger<MyClass>();
       }
}


public string Mymethod(string msg)
{
       if(msg = "a")
       {
           this.logger.LogError($"error");
       }

       return "a string";
 }

This is what finally worked for me, successful verification of LogError call:

var loggerMock = new Mock<ILogger>();
Expression<Action<ILogger>> logAction = x =>
    // You can change this up with other severity levels:
    x.Log<It.IsAnyType>(LogLevel.Error,
        It.IsAny<EventId>(),
        It.IsAny<It.IsAnyType>(),
        It.IsAny<Exception>(),
        It.IsAny<Func<It.IsAnyType, Exception, string>>());
    loggerMock.Setup(logAction).Verifiable();
    // Call a method that calls LogError here.
    loggerMock.Object.LogError("test");
    loggerMock.Verify(logAction, Times.Once);

You could do something like this

( note: this does not work in this specific case because LogError is an extension method, see below for an update ):

Mock<ILogger> _logger; // declare it somewhere where your test can access it

[Fact]
public void TestLogErrorMessage()
{
    MyClass myClass = new MyClass(MockLoggerFactory().Object);

    var result = myClass.Mymethod("a")

    _logger.Verify();
}


private Mock<ILoggerFactory> MockLoggerFactory()
{
    _logger = new Mock<ILogger>();
    _logger.Setup(log => log.LogError(It.IsAny<string>())).Verifiable();
    Mock<ILoggerFactory> mockLoggerFactory = new Mock<ILoggerFactory>(MockBehavior.Default);
    mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>()))
                           .Returns(_logger.Object);

    return mockLoggerFactory;
} 

The key thing to note is the call to Verifiable() after setting up the ILogger mock. You can read a bit more about Verifiable() in this SO question . After your test ran, you can check whether the method was called by calling .Verify() on the mock.

Depending on your needs, you could set this up in the constructor of your test class (if you need it for all/most tests) or directly inside your test method. You could also return it alongside the ILoggerFactory . The point is to hold onto the logger so you can verify against it that the method was called.

For your specific case (trying to verify calls to ILogger.LogError ) you must not mock LogError , but the underlying method ILogger.Log . That could look like this:

var formatter = new Func<It.IsAnyType, Exception, string>((a, b) => "");
m.Setup(x => x.Log<It.IsAnyType>(LogLevel.Error, 
                                 It.IsAny<EventId>(),
                                 It.IsAny<It.IsAnyType>(),
                                 It.IsAny<Exception>(), 
                                 formatter))
  .Verifiable();

Another alternative would be to make a fake implementation of ILogger and return that from the Mock<ILoggerFactory> :

mockLoggerFactory.Setup(_ => _.CreateLogger(It.IsAny<string>())
                 .Returns(new FakeLogger());

public class FakeLogger : ILogger
{
    public static bool LogErrorCalled { get; set; }

    public IDisposable BeginScope<TState>(TState state)
    {
        throw new NotImplementedException();
    }

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if(logLevel == LogLevel.Error)
        {
            LogErrorCalled = true;
        }
    }
}

And then after your test you can check whether FakeLogger.LogErrorCalled is true . This is simplified for your specific test - you can get more elaborate than this of course.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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