'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