簡體   English   中英

如何在 C# AspNet Core 中模擬 LoggerFactory

[英]How to Moq Mock a LoggerFactory in C# AspNet Core

我正在嘗試為 controller 操作編寫一些單元測試。 為此,我使用了 XUnit 和 Moq。 控制器在構造函數中注入了一個 ILoggerFactory。 一個最小起訂量如何進行測試?

我已經嘗試 mocking controller class 的記錄器,然后 mocking 向上 CreateLogger 返回模擬記錄器,但是當調用 LogInformation() function 時,我不斷收到各種測試運行時 NullReferenceExceptions。

        //   Logger that yields only disappointment...          
        var mockLogger = new Mock<ILogger<JwtController>>();
        mockLogger.Setup(ml => ml.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()));
        var mockLoggerFactory = new Mock<ILoggerFactory>();
        mockLoggerFactory.Setup(mlf => mlf.CreateLogger("JwtController")).Returns(mockLogger.Object);

我假設問題是正在調用 LogInformation,這是一種擴展方法,那么如何最小化呢?

對於它的價值:不是嘲諷的ILoggerFactory ,你也可以通過實例NullLoggerFactory 這個NullLoggerFactory將返回NullLogger實例。 根據文檔,這是一個:

什么都不做的簡約記錄器。

我只是模擬下面的 ILogger 擴展方法,並在 ILoggerFactory 設置中使用一個值函數來返回 Mock ILogger 對象。

var mockLogger = new Mock<ILogger<[YOUR_CLASS_TYPE]>>();
mockLogger.Setup(
    m => m.Log(
        LogLevel.Information,
        It.IsAny<EventId>(),
        It.IsAny<object>(),
        It.IsAny<Exception>(),
        It.IsAny<Func<object, Exception, string>>()));

var mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(() => mockLogger.Object);

這將返回您模擬的 Logger,您可以驗證對它的任何調用。 無需編寫包裝器或助手。

您甚至可以模擬IsEnabled ,這對於利用該功能的某些代碼是必需的。

        mockLogger.Setup(
            m => m.IsEnabled(
                Microsoft.Extensions.Logging.LogLevel.Debug)).Returns(true);

因為只有一種方法可以模擬(並且您必須調用),所以下面顯示了您(可能)使用的日志記錄調用,以將所有內容都指向這個確切的方法。

 catch (ArgumentOutOfRangeException argEx)
{
    // this.logger.LogError(argEx, argEx.Message); /* << this is what you would like to do, BUT it is an extension method, NOT (easily) mockable */
    Func<object, Exception, string> returnAnEmptyStringFunc = (a, b) => string.Empty;
    this.logger.Log(LogLevel.Error, ushort.MaxValue, argEx.Message, argEx, returnAnEmptyStringFunc);
    throw argEx;
}

對於需要回答這個問題而不是解決方法的任何人,我擴展了本文的工作:

https://ardalis.com/testing-logging-in-aspnet-core

包裝您使用的日志框架的任何部分,無論如何這是一個好主意。 從您自己的日志記錄界面開始:

public interface ILog<T>
{
    void LogError(Exception ex, string message, params object[] args);
    void LogInformation(string message, params object[] args);
}

然后添加實現以傳遞對框架的調用:

public class Log<T> : ILog<T>
{
    private readonly ILogger<T> logger;

    public Log(ILogger<T> logger)
    {
        this.logger = logger;
    }

    public void LogError(Exception ex, string message, params object[] args) => this.logger.LogError(ex, message, args);
    public void LogInformation(string message, params object[] args) => this.logger.LogInformation(message, args);
}

繼續,添加一個用於包裝記錄器工廠的接口:

public interface ILogFactory
{
    ILog<T> CreateLog<T>();
}

和實施:

public class LogFactory : ILogFactory
{
    private readonly ILoggerFactory loggerFactory;

    public LogFactory()
    {
        this.loggerFactory = new LoggerFactory();
    }

    public ILog<T> CreateLog<T>() => new Log<T>(new Logger<T>(this.loggerFactory));
}

這些是您應該引用Microsoft.Extensions.Logging命名空間的唯一地方。 在其他地方,使用您的ILog<T>而不是ILogger<T>和您的ILogFactory而不是ILoggerFactory 您通常會依賴注入LoggerFactory ,而不是注入包裝器:

IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<ILogFactory>(new LogFactory());

在您的主代碼中,您可以檢索此LogFactory並為您的類創建特定的Log<T>

public class MyClass
{
    public void MyMethod(IServiceCollection serviceCollection)
    {
        var serviceProvider = serviceCollection.BuildServiceProvider();
        var logFactory = this.serviceProvider.GetRequiredService<ILogFactory>();
        var log = logFactory.CreateLog<ServiceApplication>();
        log.LogInformation("Hello, World!");
    }
}

您可以想象根據需要將MyMethod的參數從IServiceCollection更改為ILogFactoryILog<MyClass> 而且 - 重點是 - 您現在可以使用以下代碼模擬上述代碼:

[Fact]
public void Test()
{
    IServiceCollection serviceCollection = new ServiceCollection();
    var mockLog = new Mock<ILog<MyClass>>();
    var mockLogFactory = new Mock<ILogFactory>();
    mockLogFactory.Setup(f => f.CreateLog<MyClass>()).Returns(mockLog.Object);
    serviceCollection.AddSingleton<ILogFactory>(mockLogFactory.Object);

    var myClass = new MyClass();
    myClass.MyMethod(serviceCollection);

    mockLog.Verify(l => l.LogInformation("Hello, World!"), Times.Once);
}

“根據您在整個應用程序中無法控制的類型,會增加耦合並經常成為問題和技術債務的根源。” - 史蒂夫·史密斯

對於任何只想提供一些記錄器以便您可以通過控制台調試代碼的人,您可以像這樣創建一個簡單的ILoggerFactory

var loggerFactory = LoggerFactory.Create(c => c
       .AddConsole()
       .SetMinimumLevel(LogLevel.Debug)
       );

//'inject' into your services
var myServiceToTest = new MyService(loggerFactory);

//or as a ILogger<T>, if thats how you're injecting
var myServiceToTest2 = new MyService(loggerFactory.CreateLogger<MyService>());

myServiceToTest.Execute();
//logger methods called in Execute should show in your IDE output console

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM