首页 > 解决方案 > 模拟自定义配置类

问题描述

我有一个自定义类用于读取 Azure Function App v2 中的配置值:

public class Config
{
    public string Key1 { get; set; }
    public string Key2 { get; set; }

    public Config()
    {
        this.Key1 = Environment.GetEnvironmentVariable("abc");
        this.Key2 = Environment.GetEnvironmentVariable("xyz");
    }
}

我已经在类的Configure方法中注册了,Startup.cs如下所示:

builder.Services.AddSingleton((s) =>
{
    return new Config();
});

现在,当我尝试模拟此类Config.cs并为其键设置值时,它会引发错误:

var mockConfiguration = new Mock<Config>();
mockConfiguration.Setup(m => m.Key1).Returns("value");

XUnit用作测试框架和MOQ模拟。如果我不想为类创建接口,我还能如何模拟我的配置类?

标签: c#asp.net-coremoqxunitazure-function-app

解决方案


这源于最初的设计问题。

该类Config与实现问题紧密耦合

Environment.GetEnvironmentVariable

隔离测试时不存在并导致异常。

正如评论中准确建议的那样,您应该利用配置模块并注册您的配置,而不是与Environment类紧密耦合。

参考: 使用委托配置简单选项

builder.Services.Configure<Config>(options => {
    options.Key1 = Environment.GetEnvironmentVariable("abc");
    options.Key2 = Environment.GetEnvironmentVariable("xyz");
});

现在这意味着该类可以简化为基本的 POCO

public class Config {
    public string Key1 { get; set; }
    public string Key2 { get; set; }
}

IOptions<Config>显式注入主题函数。

private readonly Config config;
//ctor
public MyFunction(IOptions<Config> options) { 
    config = options.Value;

    //...
}

但是,如果您不想将函数与IOptions<>接口紧密耦合,则可以通过与最初所做的类似的附加注册来解决此问题。注册您的类型并解析选项以在工厂委托中提取其值。

builder.Services.Configure<Config>(options => {
    options.Key1 = Environment.GetEnvironmentVariable("abc");
    options.Key2 = Environment.GetEnvironmentVariable("xyz");
});    
builder.Services.AddSingleton((s) => {
    return s.GetRequiredService<IOptions<Config>>().Value;
});

这将允许将Config类显式注入主题函数中,而无需使用简单 POCO 的接口。

//ctor
public MyFunction(Config config) { 
    //...
}

因此允许单独测试该功能,而不会因实现问题而产生不必要的副作用

//Arrange
var mockConfiguration = new Config() {
    Key1 = "value"
};

var subject = new MyFunction(mockConfiguration);

//...

推荐阅读