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

我有一个非常简单的方法,我需要进行单元测试。


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. 我无法更改该静态方法的实现。


我在想,也许最简单的方法是创建一个内存配置提供程序?但我不知道是否可以在不将其绑定到网络主机的情况下创建一个。


蝴蝶不菲
浏览 218回答 4
4回答

慕码人2483693

配置模块独立于网络主机相关功能。您应该能够创建一个内存配置来进行测试,而无需将其绑定到 Web 主机。查看以下示例测试public class TestConfig {&nbsp; &nbsp; [Required]&nbsp; &nbsp; public string SomeKey { get; set; }&nbsp; &nbsp; [Required] //<--NOTE THIS&nbsp; &nbsp; public string SomeOtherKey { get; set; }}//...[Fact]public void Should_Fail_Validation_For_Required_Key() {&nbsp; &nbsp; //Arrange&nbsp; &nbsp; var inMemorySettings = new Dictionary<string, string>&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; {"Email:SomeKey", "value1"},&nbsp; &nbsp; &nbsp; &nbsp; //{"Email:SomeOtherKey", "value2"}, //Purposely omitted for required failure&nbsp; &nbsp; &nbsp; &nbsp; //...populate as needed for the test&nbsp; &nbsp; };&nbsp; &nbsp; IConfiguration configuration = new ConfigurationBuilder()&nbsp; &nbsp; &nbsp; &nbsp; .AddInMemoryCollection(inMemorySettings)&nbsp; &nbsp; &nbsp; &nbsp; .Build();&nbsp; &nbsp; //Act&nbsp; &nbsp; Action act = () => configuration.GetSection("Email").GetValid<TestConfig>();&nbsp; &nbsp; //Assert&nbsp; &nbsp; ValidationException exception = Assert.Throws<ValidationException>(act);&nbsp; &nbsp; //...other assertions of validation results within exception object}在我看来,这将接近于集成测试,但理想情况下,您只是使用依赖于框架的特性来隔离扩展方法的测试。

Helenr

大多数模拟库(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 {&nbsp; &nbsp; public class Movie&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; public string Name { get; set; }&nbsp; &nbsp; &nbsp; &nbsp; public decimal Rating { get; set; }&nbsp; &nbsp; &nbsp; &nbsp; public IList<string> Stars { get; set; } //it works with collections&nbsp; &nbsp; }&nbsp; &nbsp; [Fact]&nbsp; &nbsp; public void MyTest()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var movie = new Movie {&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Name = "Some Movie",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Rating = 9,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Stars = new List<string>{"Some actress", "Some actor"}&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; var movieAsJson = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(movie));&nbsp; &nbsp; &nbsp; &nbsp; using(var stream = new MemoryStream(movieAsJson))&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var config = new ConfigurationBuilder().AddJsonStream(stream).Build();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var movieFromConfig = config.Get<Movie>();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //var sut = new SomeService(config).SomeMethodThatCallsConfig.Get<Movie>()&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}

吃鸡游戏

解决问题的一种略有不同的方法,避免了 Mock 和大量设置噪音:InMemoryConfiguration几乎给了我我需要的东西,所以我对其进行了扩展,以便您可以在构建配置后修改值(我的情况是我在构建配置时不知道所有模拟值)https://gist.github.com/martinsmith1968/9567de76d2bbe537af05d76eb39b1162底部的单元测试显示用法

慕田峪7331174

[TestClass]public class UnitTest1{&nbsp; &nbsp; [TestMethod]&nbsp; &nbsp; public void TestMethod1()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; IConfiguration mock = new MockConfiguration();&nbsp; &nbsp; &nbsp; &nbsp; var simpleObject = mock.GetValid<SimpleObject>();&nbsp; &nbsp; &nbsp; &nbsp; Assert.AreEqual(simpleObject.MyConfigStr, "123");&nbsp; &nbsp; }}public class SimpleObject{&nbsp; &nbsp; public string MyConfigStr { get; set; }}public class MockConfiguration : IConfiguration{&nbsp; &nbsp; public IConfigurationSection GetSection(string key)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return new MockConfigurationSection()&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Value = "123"&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; }&nbsp; &nbsp; public IEnumerable<IConfigurationSection> GetChildren()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var configurationSections = new List<IConfigurationSection>()&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new MockConfigurationSection()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Value = "MyConfigStr"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; return configurationSections;&nbsp; &nbsp; }&nbsp; &nbsp; public Microsoft.Extensions.Primitives.IChangeToken GetReloadToken()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; throw new System.NotImplementedException();&nbsp; &nbsp; }&nbsp; &nbsp; public string this[string key]&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get => throw new System.NotImplementedException();&nbsp; &nbsp; &nbsp; &nbsp; set => throw new System.NotImplementedException();&nbsp; &nbsp; }}public class MockConfigurationSection : IConfigurationSection{&nbsp; &nbsp; public IConfigurationSection GetSection(string key)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return this;&nbsp; &nbsp; }&nbsp; &nbsp; public IEnumerable<IConfigurationSection> GetChildren()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return new List<IConfigurationSection>();&nbsp; &nbsp; }&nbsp; &nbsp; public IChangeToken GetReloadToken()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return new MockChangeToken();&nbsp; &nbsp; }&nbsp; &nbsp; public string this[string key]&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get => throw new System.NotImplementedException();&nbsp; &nbsp; &nbsp; &nbsp; set => throw new System.NotImplementedException();&nbsp; &nbsp; }&nbsp; &nbsp; public string Key { get; }&nbsp; &nbsp; public string Path { get; }&nbsp; &nbsp; public string Value { get; set; }}public class MockChangeToken : IChangeToken{&nbsp; &nbsp; public IDisposable RegisterChangeCallback(Action<object> callback, object state)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return new MockDisposable();&nbsp; &nbsp; }&nbsp; &nbsp; public bool HasChanged { get; }&nbsp; &nbsp; public bool ActiveChangeCallbacks { get; }}public class MockDisposable : IDisposable{&nbsp; &nbsp; public void Dispose()&nbsp; &nbsp; {&nbsp; &nbsp; }}为 IConfiguration 创建了一个模拟并模仿 ConfigBinder 的行为using Microsoft.Extensions.Configuration;using Microsoft.Extensions.Primitives;添加了这两个名称空间以进行编译
打开App,查看更多内容
随时随地看视频慕课网APP