In this article, we’ll take a closer look at how we can mock IConfiguration.GetValue when writing unit tests in ASP.NET Core.
Let’s start!
Mock IConfiguration.GetValue in ASP.NET Core
Before we start mocking, we need a class that utilizes IConfiguration:
public class FinanceService(IConfiguration configuration) : IFinanceService
{
public double CalculateTotalAmount(double hours)
{
var hourlyRate = configuration.GetValue<double>("FinanceSettings:HourlyRate");
return hourlyRate * hours;
}
}
We create the FinanceService class and inject an IConfiguration instance using a primary constructor. Next, we create the CalculateTotalAmount() method that takes a double as a parameter, representing the hours worked. Then, inside the method, we use the GetValue() method from IConfiguration to get the hourly rate from our configuration file. Finally, we return the total amount.
Next, we’ll explore how we can mock the GetValue() method using two of the most popular mocking libraries – Moq and NSubstitute.
How to Mock IConfiguration.GetValue Using Moq
After we’ve created our test project, we create a new test class. After this is done, we can start writing our test:
[Theory]
[InlineData(1)]
[InlineData(1.5)]
[InlineData(1000)]
public void WhenCalculateTotalAmountIsInvoked_ThenValidResultIsReturned(double hours)
{
// Arrange
const string rate = "25.50";
var mockedSection = new Mock<IConfigurationSection>();
mockedSection.Setup(x => x.Value)
.Returns(rate);
var configuration = new Mock<IConfiguration>();
configuration.Setup(x => x.GetSection("FinanceSettings:HourlyRate"))
.Returns(mockedSection.Object);
var financeService = new FinanceService(configuration.Object);
// Act
var result = financeService.CalculateTotalAmount(hours);
// Assert
result.Should().Be(hours * double.Parse(rate));
}
We start by mocking an IConfigurationSection instance. Then we use the Moq‘s Setup() method to select the Value property of the configuration section and then use the Returns() method to return a string representation of our hourly rate. This is a vital step that should not be missed as the GetValue() method ultimately calls the GetSection() method behind the scenes.
Next, we continue with mocking IConfiguration itself. Also, we use the Setup() and Returns() methods, to specify that when we call the GetSection() method with FinanceSettings:HourlyRate as a parameter, it will return the already mocked IConfigurationSection instance.
After this is done, we create a new instance of our FinanceService class by passing the Object property of the mocked IConfiguration instance and call the CalculateTotalAmount() method to get the result. Finally, we assert that it should be equal to the multiplication result of our hourly rate and the hours worked.
How to Mock IConfiguration.GetValue Using NSubstitute
Let’s create a new class and test method so we can utilize NSubstitute:
[Theory]
[InlineData(1)]
[InlineData(1.5)]
[InlineData(1000)]
public void WhenCalculateTotalAmountIsInvoked_ThenValidResultIsReturned(double hours)
{
// Arrange
const string rate = "25.50";
var mockedSection = Substitute.For<IConfigurationSection>();
mockedSection.Value.Returns(rate);
var configuration = Substitute.For<IConfiguration>();
configuration.GetSection("FinanceSettings:HourlyRate")
.Returns(mockedSection);
var financeService = new FinanceService(configuration);
// Act
var result = financeService.CalculateTotalAmount(hours);
// Assert
result.Should().Be(hours * double.Parse(rate));
}
As we already know it’s mandatory to mock IConfigurationSection, we use NSubstitute‘s For<T>() method to create one. Here, we don’t have a setup method, so we access the Value property directly and follow it with the Returns() method to which we pass our rate as a string.
Next, we create a mock for IConfiguration and using the Returns() method again, specifying that when we call the GetSection() method with FinanceSettings:HourlyRate as a parameter, we will get our mocked configuration section.
Then, in the rest of our test, we instantiate FinanceService class, call it’s CalculateTotalAmount() method and assert that it returns the expected result.
How to Create IConfiguration Instead of Mocking It in ASP.NET Core
Mocking IConfiguration with either Moq or NSubstitute does require a bit of effort as we need to mock IConfigurationSection as well. There is a way we can create a configuration instance without relying on external libraries.
Let’s examine this approach:
[Theory]
[InlineData(1)]
[InlineData(1.5)]
[InlineData(1000)]
public void WhenCalculateTotalAmountIsInvoked_ThenValidResultIsReturned(double hours)
{
// Arrange
const string rate = "25.50";
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
{"FinanceSettings:HourlyRate", rate}
})
.Build();
var financeService = new FinanceService(configuration);
// Act
var result = financeService.CalculateTotalAmount(hours);
// Assert
result.Should().Be(hours * double.Parse(rate));
}
In a test method, we start by creating a new instance of the ConfigurationBuilder class. Then we use the AddInMemoryCollection() method that will add an in-memory collection to the configuration provider. The method requires an IEnumerable<KeyValuePair<string, string?>> instance as a parameter, to satisfy this condition we pass a Dictionary<string, string?> that has the FinanceSettings:HourlyRate as a key and the hourly rate as a value.
Finally, we call the Build() method to instantiate an IConfigurationRoot instance. It implements the IConfiguration interface so we can safely use it to create an instance of our FinanceService class and test its CalculateTotalAmount() method.
Conclusion
In this article, we saw that mocking the IConfiguration.GetValue method is a vital skill when writing unit tests. We can leverage libraries like Moq and NSubstitute, to simulate the configuration environment, ensuring our tests are isolated and reliable. But we must be sure also to mock the configuration section as it is a dependency of the GetValue method. Alternatively, we explored how to build an in-memory configuration provider without relying on external providers. By mastering both approaches, we can ensure that our applications are robust and maintainable, with reliable unit tests that accurately simulate real-world configurations.

