1

I have a custom class for reading Configuration values in my 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");
    }
}

which I've registered in the Configure method of the Startup.cs class like this:

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

Now when I'm trying to mock this Config.cs class and setup the values for it's keys, it throws error:

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

I'm using XUnit as the testing framework and MOQfor mocking. How else can I mock my configuration class if I don't want to make an interface for the class?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
The Inquisitive Coder
  • 1,085
  • 3
  • 20
  • 43
  • Of course it wont let you mock it, since the properties are non-virtual (I assume by reading the error message you should have know that). Mocking works, by inheriting the class (or interface) and overriding/implementing the property with the mocked code. Either way, You should refactor your class to make it more refactoring friendly and interface is one way to do that. If you start newing or using static classes in other parts of your code you will more and more run into such issues cause of bad software design. – Tseng Mar 13 '20 at 06:58
  • 1
    Also it snot clear why you are not using the options pattern (`IOptions` class with `.Configure(options => options.Key1 = Environment.GetEnvironmentVariable("abc"))`, then inject `IOptions`) into your classes and you would never came in that situation in the first place – Tseng Mar 13 '20 at 07:01

1 Answers1

1

This stems from an initial design issue.

The Config class is tightly coupled to implementation concerns

Environment.GetEnvironmentVariable

that are not present when testing in isolation and are causing exceptions.

As suggested accurately from the comments you should have taken advantage of the Configuration module and registered your configuration instead of tightly coupling to the Environment class.

Reference: Configure simple options with a delegate

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

This now means that the class can be simplified to a basic POCO

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

And inject IOptions<Config> explicitly into the subject function.

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

    //...
}

If you however do not want to tightly couple the function to IOptions<> interface, an additional registration similar to what was originally done can work around that. Register your type and resolve the options to extract its value in a factory delegate.

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

This would allow the Config class to be explicitly injected into the subject function without the need for an interface where a simple POCO would do.

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

thus allowing for the function to be tested in isolation without unwanted side effects from implementation concerns

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

var subject = new MyFunction(mockConfiguration);

//...
Nkosi
  • 235,767
  • 35
  • 427
  • 472