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