You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免使用Moq时LoggerFactory.Create抛出异常?

问题描述

更新相关包(大概率是Microsoft.Extensions.DependencyInjection)后,测试代码在以下行抛出异常:

var userGetLogger = LoggerFactory.Create(b => b.AddConfiguration(Configuration)).CreateLogger<UserGet>();

其中Configuration是通过new Mock<IConfiguration>().Object创建的Moq实例。

场景背景:在Azure Function UserPost中需要调用同项目的另一个Azure Function UserGet,必须符合UserGet的DI构造函数签名(需要传入ILogger<UserGet>类型的日志实例)。生产环境代码运行正常,但测试失败。

当前临时方案:给UserPost构造函数传入null,做空值判断后调用LoggerFactory.Create(b => {})(不会抛异常),但这种为了测试而修改业务代码的方式很别扭。

需求:

  1. 能否配置Moq避免该异常?
  2. 如果不行,能否在抛出异常前提前检测问题?

异常堆栈跟踪

at Microsoft.Extensions.Configuration.ConfigurationBinder.GetValue[T](IConfiguration configuration, String key, T defaultValue)
   at Microsoft.Extensions.Logging.LoggerFilterConfigureOptions.LoadDefaultConfigValues(LoggerFilterOptions options)
   at Microsoft.Extensions.Logging.LoggerFilterConfigureOptions.Configure(LoggerFilterOptions options)
   at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name)
   at Microsoft.Extensions.Options.OptionsMonitor`1.<>c__DisplayClass10_0.<Get>b__0()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.get_Value()
   at Microsoft.Extensions.Options.OptionsCache`1.GetOrAdd(String name, Func`1 createOptions)
   at Microsoft.Extensions.Options.OptionsMonitor`1.Get(String name)
   at Microsoft.Extensions.Options.OptionsMonitor`1.get_CurrentValue()
   at Microsoft.Extensions.Logging.LoggerFactory..ctor(IEnumerable`1 providers, IOptionsMonitor`1 filterOption, IOptions`1 options)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.Extensions.Logging.LoggerFactory.Create(Action`1 configure)
   at UserAccess.Functions.UserPost.<Run>d__10.MoveNext() in H:\Repos\EasySpect\Portal\UserAccess\Project\Functions\UserPost.cs:line 43

UserGet类代码片段

public class UserGet
{
    IUsers Api { get; }

    ILogger Logger { get; }

    public UserGet(IUsers api, ILogger<UserGet> logger)
    {
        Api = api;
        Logger = logger;
    }

    [FunctionName(nameof(UserGet))]
    public async Task<Guid> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "user/{email}")] HttpRequest req, string email)
    {
        if (email == null)
        {
            Logger.LogWarning("User email is not present in the URL");
            return Guid.Empty;
        }

        return await GetUser(email);
    }
}
解决方案

一、配置Moq模拟IConfiguration避免异常

异常根源是LoggerFilterConfigureOptions尝试从配置中读取日志过滤相关的默认值(比如Logging:LogLevel节点),而空的MoqIConfiguration无法正确响应这些读取操作。可以给Moq配置必要的节点模拟:

var configMock = new Mock<IConfiguration>();
// 模拟日志配置节点,设置默认日志级别
configMock.Setup(c => c.GetSection("Logging:LogLevel:Default")).Returns(Mock.Of<IConfigurationSection>(s => s.Value == "Information"));
configMock.Setup(c => c.GetSection("Logging:LogLevel:Microsoft")).Returns(Mock.Of<IConfigurationSection>(s => s.Value == "Warning"));

// 如果还需要其他节点,按需添加,比如特定类的日志级别
configMock.Setup(c => c.GetSection("Logging:LogLevel:UserAccess.Functions.UserGet")).Returns(Mock.Of<IConfigurationSection>(s => s.Value == "Information"));

var Configuration = configMock.Object;

这样当LoggerFactory尝试读取配置时,Moq能返回预期的值,避免ConfigurationBinder.GetValue抛出异常。

二、直接模拟ILogger替代手动创建

既然是测试场景,完全可以直接MockILogger<UserGet>,无需手动通过LoggerFactory创建,更简洁且避免配置依赖:

// 在测试中直接创建Mock的ILogger<UserGet>
var loggerMock = new Mock<ILogger<UserGet>>();
// 如果需要验证日志调用,可以添加Setup,否则空Mock即可

// 实例化UserGet时传入该Mock
var userGet = new UserGet(apiMock.Object, loggerMock.Object);

这种方式彻底绕开了LoggerFactory和IConfiguration的依赖问题,测试代码更聚焦业务逻辑,也不用修改生产代码。

三、提前检测配置有效性(备选方案)

如果一定要保留手动创建LoggerFactory的逻辑,可以在调用前检测Configuration是否包含必要节点:

bool IsLoggingConfigValid(IConfiguration config)
{
    return config.GetSection("Logging:LogLevel").Exists() 
           && !string.IsNullOrEmpty(config.GetSection("Logging:LogLevel:Default").Value);
}

// 使用时
var loggerFactory = IsLoggingConfigValid(Configuration) 
    ? LoggerFactory.Create(b => b.AddConfiguration(Configuration)) 
    : LoggerFactory.Create(b => {});
var userGetLogger = loggerFactory.CreateLogger<UserGet>();

这段代码可以放在生产代码中,既不影响正常环境(配置有效时走正常逻辑),测试环境中配置无效时自动降级到无配置的LoggerFactory,避免异常,同时不用做null判断,代码更干净。


内容的提问来源于stack exchange,提问作者noontz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 16:25:09