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.