'How to Unit Test Method Calling IConfiguration.Get<T> extension
I have this very simple method that I need to unit test.
public static class ValidationExtensions
{
public static T GetValid<T>(this IConfiguration configuration)
{
var obj = configuration.Get<T>();
Validator.ValidateObject(obj, new ValidationContext(obj), true);
return obj;
}
}
The problem is that configuration.Get<T> is a static extension method and doesn't belong to IConfiguration. I can't change the implementation of that static method.
I'm thinking, perhaps the simplest way is to create a Memory Configuration Provider? But I don't know if I can create one without binding it to a web host.
Solution 1:[1]
A slightly different way of tackling the problem, avoiding Mock and lots of setup noise:
The InMemoryConfiguration almost gave me what I needed, so I extended it so that you can amend values after building the configuration (Situation I had I didn’t know all the mocked values at the time of building the Configuration)
https://gist.github.com/martinsmith1968/9567de76d2bbe537af05d76eb39b1162
Unit test at the bottom shows usage
Solution 2:[2]
Most of mocking libraries(Moq, FakeItEasy, etc) can't mock extension methods.
So, you have to 'fill' your IConfiguration in a way that a IConfiguration.Get<T> returns an instance of T. Nkoski answer works for a lot of scenarios, but not if you need to test a code that calls IConfiguration.Get<T> you can use the example bellow:
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Xunit;
public class TestClass {
public class Movie
{
public string Name { get; set; }
public decimal Rating { get; set; }
public IList<string> Stars { get; set; } //it works with collections
}
[Fact]
public void MyTest()
{
var movie = new Movie {
Name = "Some Movie",
Rating = 9,
Stars = new List<string>{"Some actress", "Some actor"}
};
var movieAsJson = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(movie));
using(var stream = new MemoryStream(movieAsJson))
{
var config = new ConfigurationBuilder().AddJsonStream(stream).Build();
var movieFromConfig = config.Get<Movie>();
//var sut = new SomeService(config).SomeMethodThatCallsConfig.Get<Movie>()
}
}
}
Solution 3:[3]
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
IConfiguration mock = new MockConfiguration();
var simpleObject = mock.GetValid<SimpleObject>();
Assert.AreEqual(simpleObject.MyConfigStr, "123");
}
}
public class SimpleObject
{
public string MyConfigStr { get; set; }
}
public class MockConfiguration : IConfiguration
{
public IConfigurationSection GetSection(string key)
{
return new MockConfigurationSection()
{
Value = "123"
};
}
public IEnumerable<IConfigurationSection> GetChildren()
{
var configurationSections = new List<IConfigurationSection>()
{
new MockConfigurationSection()
{
Value = "MyConfigStr"
}
};
return configurationSections;
}
public Microsoft.Extensions.Primitives.IChangeToken GetReloadToken()
{
throw new System.NotImplementedException();
}
public string this[string key]
{
get => throw new System.NotImplementedException();
set => throw new System.NotImplementedException();
}
}
public class MockConfigurationSection : IConfigurationSection
{
public IConfigurationSection GetSection(string key)
{
return this;
}
public IEnumerable<IConfigurationSection> GetChildren()
{
return new List<IConfigurationSection>();
}
public IChangeToken GetReloadToken()
{
return new MockChangeToken();
}
public string this[string key]
{
get => throw new System.NotImplementedException();
set => throw new System.NotImplementedException();
}
public string Key { get; }
public string Path { get; }
public string Value { get; set; }
}
public class MockChangeToken : IChangeToken
{
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{
return new MockDisposable();
}
public bool HasChanged { get; }
public bool ActiveChangeCallbacks { get; }
}
public class MockDisposable : IDisposable
{
public void Dispose()
{
}
}
created a mock for IConfiguration and mimic the behaviour of ConfigBinder
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
added these two namespace for it compile
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Martin1968 |
| Solution 2 | Talles Santana |
| Solution 3 |
