如何仅模拟 ILogger<T> 一次,而不需要为每个通用 T 实现创建新的模拟?

Tar*_*Tar 3 c# dependency-injection moq mocking inversion-of-control

长话短说:

由于使用Moq不可能模拟开放泛型类型,因此创建实际的模拟是不可避免的。这是我最终使用的最小模拟:

internal interface ILoggerMock {
    public List<(LogLevel logLevel, string msg)> Logs { get; }
}

public class LoggerMock<T> : ILoggerMock, ILogger<T> {
    public List<(LogLevel logLevel, string msg)> Logs { get; } = new();
    public IDisposable BeginScope<TState>(TState state) => throw new NotImplementedException();
    public bool IsEnabled(LogLevel logLevel) => throw new NotImplementedException();
    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) {
        var formattedLogMessage = formatter(state, exception);
        Logs.Add((logLevel, formattedLogMessage));
    }
}
Run Code Online (Sandbox Code Playgroud)

通过此注册:

var myServiceProvider = new ServiceCollection()
    // ... more registrations as needed ...
    .AddSingleton(typeof(ILogger<>), typeof(LoggerMock<>))
    // ... more registrations as needed ...
    .BuildServiceProvider()

_loggerMock = (ILoggerMock) myServiceProvider.GetService(typeof(ILogger<Answer>));
Run Code Online (Sandbox Code Playgroud)

原问题:

每个类都会注入每个类的ILogger<T>位置,例如:T

public class Question {
    public Question(ILogger<Question> logger) { /* ... */ }
}

public class Answer {
    public Answer(ILogger<Answer> logger) { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)

但这意味着我必须为每个ILogger<T>消费者创建一个模拟:

var questionLoggerMock = new Mock<ILogger<Question>>();
var answerLoggerMock = new Mock<ILogger<Answer>>();

var services = new ServiceCollection()
    .AddScoped<ILogger<Question>>(_ => questionLoggerMock.Object)
    .AddScoped<ILogger<Answer>>(_ => answerLoggerMock.Object)
    /*
    .
    ... all other ILogger consumers...
    .
    */
Run Code Online (Sandbox Code Playgroud)

我尝试了很多技巧,例如.AddSingleton<ILogger<object>>(_ => omniLoggerMock.Object).AddSingleton(typeof(ILogger<>), _ => omniLoggerMock.Object),但似乎都不起作用(如果编译,它们会在运行时失败)。

有没有一个简单的解决方案,或者我注定要为每个泛型类型创建一个新的模拟?

小智 5

如果您不需要测试/模拟调用,最简单的方法是使用NullLogger

services.Add(
    ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(NullLogger<>)));
Run Code Online (Sandbox Code Playgroud)

如果您需要模拟部分,因为您需要查看是否记录了正确的内容,您可以这样做:


var mocker = new Mock<ILogger>();
services.Add(
    ServiceDescriptor.Singleton(typeof(ILogger), mocker.Object));
services.Add(
    ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(ProxyLogger<>)));

public class ProxyLogger<T>: ILogger<T>{
    private readonly ILogger proxee;

    public ProxyLogger(ILogger proxee)
    {
        this.proxee = proxee;
    }

    public IDisposable BeginScope<TState>(TState state) 
        => proxee.BeginScope<TState>(state);

    public bool IsEnabled(LogLevel logLevel)
        => proxee.IsEnabled(logLevel);

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        => proxee.Log(logLevel, eventId, state, exception, formatter);
}

Run Code Online (Sandbox Code Playgroud)