124

I tried in vain to mock a top-level (not part of any section) configuration value (.NET Core's IConfiguration). For example, neither of these will work (using NSubstitute, but it would be the same with Moq or any mock package I believe):

var config = Substitute.For<IConfiguration>();
config.GetValue<string>(Arg.Any<string>()).Returns("TopLevelValue");
config.GetValue<string>("TopLevelKey").Should().Be("TopLevelValue"); // nope
// non generic overload
config.GetValue(typeof(string), Arg.Any<string>()).Returns("TopLevelValue");
config.GetValue(typeof(string), "TopLevelKey").Should().Be("TopLevelValue"); // nope

In my case, I also need to call GetSection from this same config instance.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
dudeNumber4
  • 4,217
  • 7
  • 40
  • 57

10 Answers10

272

You can use an actual Configuration instance with in-memory data.

//Arrange
var inMemorySettings = new Dictionary<string, string> {
    {"TopLevelKey", "TopLevelValue"},
    {"SectionName:SomeKey", "SectionValue"},
    //...populate as needed for the test
};

IConfiguration configuration = new ConfigurationBuilder()
    .AddInMemoryCollection(inMemorySettings)
    .Build();


//...

Now it is a matter of using the configuration as desired to exercise the test

//...

string value = configuration.GetValue<string>("TopLevelKey");

string sectionValue = configuration.GetSection("SectionName").GetValue<string>("SomeKey");

//...

Reference: Memory Configuration Provider

Pang
  • 9,564
  • 146
  • 81
  • 122
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 1
    I should have hit this post hours earlier, NSubstitute while returning nothing on IConfiguration.GetValue, it breaks on GetValue with an InvalidOperationException: 'Failed to convert configuration value at '' to type 'System.Boolean'.' although the return value is specified correctly. – Tony Thomas Jul 27 '21 at 16:52
  • 2
    // example about how to configure a array setting. var inMemorySettings = new Dictionary { {"SomeArrayKey:0", "value0"}, {"SomeArrayKey:1", "value1"}, {"SomeArrayKey:2", "value2"} }; – Diri Jianwei Guo Jul 06 '22 at 01:58
  • If you have nullable enabled the Dictionary should be declared as Dictionary – Bo Christian Skjøtt May 26 '23 at 07:26
  • 1
    To configure nested settings (like e.g the DefaultConnectionString in the section ConnectionStrings one can simply add a value to the dictionary like this: var inMemorySettings = new Dictionary { { "ConnectionStrings:DefaultConnection", "YOUR CONNECTION DETAILS" }, }; – ozerfrettelter Jun 24 '23 at 10:06
  • This worked like magic! Thanks! – MichaelEr Aug 10 '23 at 09:12
37

I do not have idea about NSubstitute, but this is how we can do in Moq. Aproach is same in either cases.

GetValue<T>() internally makes use of GetSection().

You can Mock GetSection and return your Own IConfigurationSection.

This includes two steps.

1). Create a mock for IConfigurationSection (mockSection) & Setup .Value Property to return your desired config value.

2). Mock .GetSection on Mock< IConfiguration >, and return the above mockSection.Object

Mock<IConfigurationSection> mockSection = new Mock<IConfigurationSection>();
mockSection.Setup(x=>x.Value).Returns("ConfigValue");

Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();
mockConfig.Setup(x=>x.GetSection("ConfigKey")).Returns(mockSection.Object);
Shekar Reddy
  • 616
  • 7
  • 18
  • I tried to set values for two keys in IConfiguration using two different mockSection objects. Still the value of the first key was overridden by the second value and the value of other key was 0. How to solve this problem? – Tejashree Mar 19 '21 at 14:09
  • Make sure you use the right expression in k=>k=="configkey" istead of .it.isany. – Shekar Reddy Mar 19 '21 at 14:12
  • That's how I wrote: `mockConfig.Setup(x => x.SetSection(It.Is(k => k == "Key1"))).Returns(mocksection1.object); mockConfig.Setup(x => x.SetSection(It.Is(k => k == "Key2"))).Returns(mocksection2.object);` – Tejashree Mar 19 '21 at 15:01
  • SetSection or GetSection? – Shekar Reddy Mar 19 '21 at 15:30
  • for me mockSection.Setup(x=>x.Value).Returns("ConfigValue"); is throwing null object reference exception – Ashu May 10 '22 at 10:05
  • did you initialize mockSection – Shekar Reddy May 10 '22 at 16:00
  • 2
    `It.Is(k=>k=="ConfigKey")` can be simplified to just `"ConfigKey"`. – Johnathan Barclay Sep 23 '22 at 14:57
  • You also need to mock `GetSection` method even for `mockSection`, because `ConfiguraitonBinder.GetValue` internally calls `GetSection`. Otherwise you get a null reference exception. – II ARROWS May 08 '23 at 15:42
21

Mock IConfiguration

Mock<IConfiguration> config = new Mock<IConfiguration>();

SetupGet

config.SetupGet(x => x[It.Is<string>(s => s == "DeviceTelemetryContainer")]).Returns("testConatiner");
config.SetupGet(x => x[It.Is<string>(s => s == "ValidPowerStatus")]).Returns("On");
JakeMc
  • 413
  • 6
  • 13
Lokesh Chikkala
  • 211
  • 2
  • 2
  • 8
    Welcome to Stackoverflow. Can you please elaborate on your answer, to add some context? – jhoepken Jun 08 '21 at 12:11
  • 1
    This works well, only change I did here was to use constants i.e. `config.SetupGet(x => x[Constants.SOME_KEY]).Returns("testConatiner");` Tx! – AaBa Dec 06 '21 at 01:06
  • I was unable to figure out how to get this to work for returning an int. configuration.GetValue("myConfig"); The only way I was able to do that was mockConfigurationSection2.SetupGet(x => x.Value).Returns("2"); mockConfiguration.Setup(x => x.GetSection("myConfig")).Returns(mockConfigurationSection2.Object); – Owen Ivory Aug 29 '23 at 22:47
5

IConfiguration.GetSection<T> must be mocked indirectly. I don't fully understand why because NSubstitute, if I understand correctly, creates its own implementation of an interface you're mocking on the fly (in memory assembly). But this seems to be the only way it can be done. Including a top-level section along with a regular section.

var config = Substitute.For<IConfiguration>();
var configSection = Substitute.For<IConfigurationSection>();
var configSubSection = Substitute.For<IConfigurationSection>();
configSubSection.Key.Returns("SubsectionKey");
configSubSection.Value.Returns("SubsectionValue");
configSection.GetSection(Arg.Is("SubsectionKey")).Returns(configSubSection);
config.GetSection(Arg.Is("TopLevelSectionName")).Returns(configSection);

var topLevelSection = Substitute.For<IConfigurationSection>();
topLevelSection.Value.Returns("TopLevelValue");
topLevelSection.Key.Returns("TopLevelKey");
config.GetSection(Arg.Is<string>(key => key != "TopLevelSectionName")).Returns(topLevelSection);

// GetValue mocked indirectly.
config.GetValue<string>("TopLevelKey").Should().Be("TopLevelValue");
config.GetSection("TopLevelSectionName").GetSection("SubsectionKey").Value.Should().Be("SubsectionValue");
Pang
  • 9,564
  • 146
  • 81
  • 122
dudeNumber4
  • 4,217
  • 7
  • 40
  • 57
3

I could imagine in some rare scenarios it is needed but, in my humble opinion, most of the time, mocking IConfiguration highlight a code design flaw.

You should rely as much as possible to the option pattern to provide a strongly typed access to a part of your configuration. Also it will ease testing and make your code fail during startup if your application is misconfigured (instead than at runtime when the code reading IConfiguration is executed).

If you really(really) need to mock it then I would advice to not mock it but fake it with an in-memory configuration as explained in @Nkosi's answer

asidis
  • 1,374
  • 14
  • 24
1

While Nkosi's answer works great for simple structures, sometimes you want to be able to have more complex objects (like arrays) without repeating the whole section path and to be able to use the expected types themselves. If you don't really care too much about performance (and should you in your unit tests?) then this extension method might be helpful.

public static void AddObject(this IConfigurationBuilder cb, object model) {
    cb.AddJsonStream(new MemoryStream(Encoding.UTF8.GetString(JsonConvert.SerializeObject(model))));
}

And then use it like this

IConfiguration configuration = new ConfigurationBuilder()
    .AddObject(new {
       SectionName = myObject
    })
    .Build();
F.A.
  • 602
  • 1
  • 7
  • 15
0

Use SetupGet method to mock the Configuration value and return any string.

var configuration = new Mock<IConfiguration>();
configuration.SetupGet(x => x[It.IsAny<string>()]).Returns("the string you want to return");
Dev-lop-er
  • 578
  • 1
  • 7
  • 16
0

We need to mock IConfiguration.GetSection wichs is executed within GetValue extension method.

You inject the IConfiguration:

private readonly Mock<IConfiguration> _configuration;

and the in the //Arrange Section:

_configuration.Setup(c => c.GetSection(It.IsAny())).Returns(new Mock().Object);

It worked like a charm for me.

David Castro
  • 1,773
  • 21
  • 21
0

I've found this solution to work reliably for me for my XUnit C# tests & corresponding C# code:

In Controller

string authConnection = this._config["Keys:AuthApi"] + "/somePathHere/Tokens/jwt";

In XUnit Test

Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();
mockConfig.SetupGet(x => x[It.Is<string>(s => s == "Keys:AuthApi")]).Returns("some path here");
BrianInPhx
  • 101
  • 1
  • 3
0

For simple top level key and value, I used this code and it worked for me:

var _configuration = Substitute.For<IConfiguration>();
_configuration["TopLevelKey"].Returns("TopLevelValue");
Saket Kumar
  • 4,363
  • 4
  • 32
  • 55