C# how to mock Configuration.GetSection("foo:bar").Get<List<string>>()
Asked Answered
U

8

41

I have a list like following in config.json file `

{
  "foo": {
    "bar": [
      "1",
      "2",
      "3"
    ]
  }
}`

I am able to get the list at run-time using

Configuration.GetSection("foo:bar").Get<List<string>>()

I want to mock the configuration.GetSection to write unit test.

Following syntax is failing

mockConfigRepo
    .SetupGet(x => x.GetSection("reportLanguageSettings:reportLanguageList").Get<List<string>>())
    .Returns(reportLanguages);
Unbuild answered 6/5, 2018 at 15:46 Comment(3)
You cannot mock it unless you abstract it out into an interface or a virtual method. What is mockConfigRepo mocking?Novick
It's mockConfigRepo a Mock object of IConfigurationRoot? And reportLanguages it's a list of strings? Could you specify the error?Transalpine
The method IConfigurationSection.Get<T> is an extension method. You cannot mock extension methods.Transalpine
D
46

I was able to solve it using ConfigurationBuilder. Hope this will help

  var appSettings = @"{""AppSettings"":{
            ""Key1"" : ""Value1"",
            ""Key2"" : ""Value2"",
            ""Key3"" : ""Value3""
            }}";

  var builder = new ConfigurationBuilder();

  builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(appSettings)));

  var configuration= builder.Build();
Depone answered 22/6, 2020 at 10:20 Comment(3)
AddInMemoryCollection is nice too.Meridethmeridian
I needed to mock instead of use ConfigurationBuilder as I don't think I could get ConfigurationBuilder to work with an actual array (of strings in my case). Pasha Bank's answer is the most appropraite to the question as it addresses arrays and Mocks.Fuzee
Spendid, easy to use ;-)B
A
37

I've encountered same issue and found that I needed to create a mock IConfigurationSection for every element in the array, as well as the array node itself, and then setup the parent node to return children, and children to return their values. In OP example, it would look like this:

        var oneSectionMock = new Mock<IConfigurationSection>();
        oneSectionMock.Setup(s => s.Value).Returns("1");
        var twoSectionMock = new Mock<IConfigurationSection>();
        twoSectionMock.Setup(s => s.Value).Returns("2");
        var fooBarSectionMock = new Mock<IConfigurationSection>();
        fooBarSectionMock.Setup(s => s.GetChildren()).Returns(new List<IConfigurationSection> { oneSectionMock.Object, twoSectionMock.Object });
        _configurationMock.Setup(c => c.GetSection("foo:bar")).Returns(fooBarSectionMock.Object);

P.S. I'm using Moq, so please translate to your mock library of choice.

P.P.S. If you are interested in why this ends up working, what unmockable Get() method does, or have a more complex scenario than OP, reading this class may be helpful: https://github.com/aspnet/Extensions/blob/release/2.1/src/Configuration/Config.Binder/src/ConfigurationBinder.cs

Abbreviation answered 15/3, 2019 at 19:18 Comment(1)
you should be a maniac to implement this kind of parameter structureOsher
L
19

In general, if you have a key/value at the root level and you want to mock this piece of code:

var threshold = _configuration.GetSection("RootLevelValue").Value;

you can do:

var mockIConfigurationSection = new Mock<IConfigurationSection>();
mockIConfigurationSection.Setup(x => x.Key).Returns("RootLevelValue");
mockIConfigurationSection.Setup(x => x.Value).Returns("0.15");
_mockIConfiguration.Setup(x => x.GetSection("RootLevelValue")).Returns(mockIConfigurationSection.Object);

If the key/value is not at the root level, and you want to mock a code that is like this one:

var threshold = _configuration.GetSection("RootLevelValue:SecondLevel").Value;

you have to mock Path as well:

var mockIConfigurationSection = new Mock<IConfigurationSection>();
mockIConfigurationSection.Setup(x => x.Path).Returns("RootLevelValue");
mockIConfigurationSection.Setup(x => x.Key).Returns("SecondLevel");
mockIConfigurationSection.Setup(x => x.Value).Returns("0.15");

and so on for the third level:

var threshold = _configuration.GetSection("RootLevelValue:SecondLevel:ThirdLevel").Value;

you have to mock Path as well:

var mockIConfigurationSection = new Mock<IConfigurationSection>();
mockIConfigurationSection.Setup(x => x.Path).Returns("RootLevelValue:SecondLevel");
mockIConfigurationSection.Setup(x => x.Key).Returns("ThirdLevel");
mockIConfigurationSection.Setup(x => x.Value).Returns("0.15");
Lamed answered 6/6, 2020 at 9:59 Comment(2)
with that mock i am unable to cast to type... mockIConfigurationSection.Object.Get<SomeClass>(); is null even when mockIConfigurationSection.Object contains json of the instance of SomeClassCoprophilous
@SashaBond I have the same issue and the problem here is that Get<..> is an extension method (or, in other words, a static method) and Moq does not provide support to "override" a static method. For this, I've choosen to read a configuration from memory stream as reported in this answer https://mcmap.net/q/394892/-c-how-to-mock-configuration-getsection-quot-foo-bar-quot-get-lt-list-lt-string-gt-gtLeukocyte
F
12

You can try alternative ways. For example, you can try to create an instance of ConfigurationBuilder in your test class:

string projectPath = AppDomain.CurrentDomain.BaseDirectory.Split(new String[] { @"bin\" }, StringSplitOptions.None)[0];
IConfiguration config = new ConfigurationBuilder()
   .SetBasePath(projectPath)
   .AddJsonFile("config.json")
   .Build();

Note: Please don't forget to add your config.json file to your test project too.

Fabien answered 30/8, 2018 at 13:23 Comment(4)
It is an alternative way of mocking, but it surely works, and is easier as well.Pursuant
I think it is not a mocking because you are depending on an external asset. If this asset does not exist the test will give a different result, that is bad.Dearr
@RenatoRamosNascimento Unit test are just a bunch of setup to test a specific code unit. Ideally yes, there should be a way to set it up to not depend on an external file (See Ahamed Ishak's answer and related comments below), but having to make sure you have the external file prepared for the test is not much different than making sure you have all your mocks configured imho.Haustorium
Your unit tests should not depends on external sources like files or database. It breaks the main advantage of unit tests.Novelist
T
9

Just to add on Ahamed Ishak answer. Convert actual objects to JSON would clean up the code and types are respected. Avoid string typo errors, etc.

        var appSettings = JsonConvert.SerializeObject(new
        {
            Security = new SecurityOptions {Salt = "test"}
        });

        var builder = new ConfigurationBuilder();

        builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(appSettings)));

        var configuration = builder.Build();
Thalamencephalon answered 15/11, 2020 at 11:41 Comment(0)
O
0

For those using FakeItEasy, you can simply do:

var fakeConfiguration = A.Fake<IConfiguration>();
var fakeConfigurationSection = A.Fake<IConfigurationSection>();

fakeConfigurationSection["bar"] = "Your desired value here";

A.CallTo(() => fakeConfiguration.GetSection("foo"))
                .Returns(fakeConfigurationSection);

This might be helpful for getting ConnectionStrings, just replace foo by ConnectionStrings and bar by your connection string key

Otter answered 20/6, 2023 at 18:3 Comment(0)
V
0

You need to add the Microsoft.Extensions.Configuration.Json NuGet package to have access to the method AddJsonStream():

dotnet add package Microsoft.Extensions.Configuration.Json
Volunteer answered 30/1, 2024 at 16:8 Comment(0)
S
0

Can also use AddInMemoryCollection and pass it a dictionary like so:

var inMemorySettings = new Dictionary<string, string>
{
    { "foo:bar", "abc" },
    { "foo:baz", "definitely" },
};
IConfiguration configuration = new ConfigurationBuilder()
    .AddInMemoryCollection(inMemorySettings)
    .Build();
Sikes answered 8/5, 2024 at 20:2 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.