【问题标题】:Mocking custom Configuration class模拟自定义配置类
【发布时间】:2020-03-13 22:03:06
【问题描述】:

我有一个自定义类用于读取 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");
    }
}

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

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

现在,当我尝试模拟这个 Config.cs 类并为其键设置值时,它会抛出错误:

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

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

【问题讨论】:

  • 当然它不会让你模拟它,因为属性是非虚拟的(我假设通过阅读错误消息你应该知道这一点)。模拟工作,通过继承类(或接口)并用模拟代码覆盖/实现属性。无论哪种方式,您都应该重构您的类以使其对重构更加友好,而接口是一种方法。如果您开始在代码的其他部分新建或使用静态类,您将越来越多地遇到此类导致软件设计不佳的问题。
  • 还不清楚为什么你不使用选项模式(IOptions&lt;T&gt; 类和.Configure&lt;Config&gt;(options =&gt; options.Key1 = Environment.GetEnvironmentVariable("abc")),然后注入IOptions&lt;Config&gt;)到你的类中,你一开始就不会遇到这种情况地点

标签: c# asp.net-core moq xunit azure-function-app


【解决方案1】:

这源于最初的设计问题。

Config 类与实现问题紧密耦合

Environment.GetEnvironmentVariable

独立测试时不存在并导致异常。

根据 cmets 的准确建议,您应该利用配置模块并注册您的配置,而不是与 Environment 类紧密耦合。

参考:Configure simple options with a delegate

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&lt;Config&gt; 显式注入主题函数。

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

    //...
}

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

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);

//...

【讨论】:

    猜你喜欢
    • 2017-05-21
    • 1970-01-01
    • 2016-03-27
    • 2012-07-26
    • 2012-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多