Насмешливый пользовательский класс конфигурации - PullRequest
1 голос
/ 13 марта 2020

У меня есть собственный класс для чтения значений конфигурации в моем Azure приложении функции 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 для насмешек. Как еще я могу издеваться над моим классом конфигурации, если я не хочу создавать интерфейс для класса?

1 Ответ

1 голос
/ 13 марта 2020

Это связано с первоначальной проблемой дизайна.

Класс Config тесно связан с проблемами реализации

Environment.GetEnvironmentVariable

, которые отсутствуют при тестировании по отдельности и вызывают исключения.

Как точно указано в комментарии, вы должны были воспользоваться модулем конфигурации и зарегистрировать свою конфигурацию вместо того, чтобы тесно связывать ее с классом Environment.

Ссылка: Настройка простых параметров с делегатом

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

Теперь это означает, что класс можно упростить до базовых c 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);

//...
...