为了账号安全,请及时绑定邮箱和手机立即绑定

如何单元测试方法调用 IConfiguration.Get<T> 扩展

如何单元测试方法调用 IConfiguration.Get<T> 扩展

C#
蝴蝶不菲 2022-10-23 15:03:26
我有一个非常简单的方法,我需要进行单元测试。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;    }}问题是这configuration.Get<T>是一个静态扩展方法,不属于IConfiguration. 我无法更改该静态方法的实现。我在想,也许最简单的方法是创建一个内存配置提供程序?但我不知道是否可以在不将其绑定到网络主机的情况下创建一个。
查看完整描述

4 回答

?
慕码人2483693

TA贡献1860条经验 获得超9个赞

配置模块独立于网络主机相关功能。


您应该能够创建一个内存配置来进行测试,而无需将其绑定到 Web 主机。


查看以下示例测试


public class TestConfig {

    [Required]

    public string SomeKey { get; set; }

    [Required] //<--NOTE THIS

    public string SomeOtherKey { get; set; }

}


//...


[Fact]

public void Should_Fail_Validation_For_Required_Key() {

    //Arrange

    var inMemorySettings = new Dictionary<string, string>

    {

        {"Email:SomeKey", "value1"},

        //{"Email:SomeOtherKey", "value2"}, //Purposely omitted for required failure

        //...populate as needed for the test

    };


    IConfiguration configuration = new ConfigurationBuilder()

        .AddInMemoryCollection(inMemorySettings)

        .Build();


    //Act

    Action act = () => configuration.GetSection("Email").GetValid<TestConfig>();


    //Assert

    ValidationException exception = Assert.Throws<ValidationException>(act);

    //...other assertions of validation results within exception object

}

在我看来,这将接近于集成测试,但理想情况下,您只是使用依赖于框架的特性来隔离扩展方法的测试。


查看完整回答
反对 回复 2022-10-23
?
Helenr

TA贡献1780条经验 获得超3个赞

大多数模拟库(Moq、FakeItEasy 等)都不能模拟扩展方法。


因此,您必须以 aIConfiguration.Get<T>返回 T 实例的方式“填充”您的 IConfiguration。Nkoski 答案适用于很多场景,但如果您需要测试调用的代码,IConfiguration.Get<T>则可以使用下面的示例:


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>()

        }

    }

}


查看完整回答
反对 回复 2022-10-23
?
吃鸡游戏

TA贡献1829条经验 获得超7个赞

解决问题的一种略有不同的方法,避免了 Mock 和大量设置噪音:

InMemoryConfiguration几乎给了我我需要的东西,所以我对其进行了扩展,以便您可以在构建配置后修改值(我的情况是我在构建配置时不知道所有模拟值)

https://gist.github.com/martinsmith1968/9567de76d2bbe537af05d76eb39b1162

底部的单元测试显示用法


查看完整回答
反对 回复 2022-10-23
?
慕田峪7331174

TA贡献1828条经验 获得超13个赞

[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()

    {

    }

}

为 IConfiguration 创建了一个模拟并模仿 ConfigBinder 的行为


using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.Primitives;

添加了这两个名称空间以进行编译


查看完整回答
反对 回复 2022-10-23
  • 4 回答
  • 0 关注
  • 128 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信