如何模拟 IConfiguration.GetValue
How to mock IConfiguration.GetValue
我试图模拟顶级(不属于任何部分)配置值(.NET Core 的 IConfiguration),但没有成功。例如,这些都不起作用(使用 NSubstitute,但它与 Moq 或我认为的任何模拟包相同):
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
在我的例子中,我还需要从同一个配置实例调用 GetSection
。
IConfiguration.GetSection<T>
必须间接模拟。我不完全理解为什么因为 NSubstitute,如果我理解正确的话,创建它自己的你正在动态模拟的接口的实现(在内存程序集中)。但这似乎是唯一可以做到的方法。包括顶级部分和常规部分。
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");
您可以使用内存中数据的实际配置实例。
//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();
//...
现在就是根据需要使用配置来练习测试了
//...
string value = configuration.GetValue<string>("TopLevelKey");
string sectionValue = configuration.GetSection("SectionName").GetValue<string>("SomeKey");
//...
我不知道 NSubstitute,但这就是我们在 Moq 中可以做的。
在这两种情况下,方法都是相同的。
您可以模拟 GetSection
和 return 您自己的 IConfigurationSection
。
这包括两个步骤。
1).为 IConfigurationSection
(mockSection)创建模拟并设置 .Value
属性 到 return 你想要的配置值。
2).在 Mock 上 Mock .GetSection
,并且 return 以上 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(It.Is<string>(k=>k=="ConfigKey"))).Returns(mockSection.Object);
GetValue<T>()
内部使用 GetSection()
.
模拟 IConfiguration
Mock<IConfiguration> config = new Mock<IConfiguration>();
设置获取
config.SetupGet(x => x[It.Is<string>(s => s == "DeviceTelemetryContainer")]).Returns("testConatiner");
config.SetupGet(x => x[It.Is<string>(s => s == "ValidPowerStatus")]).Returns("On");
我可以想象在一些罕见的情况下需要它,但以我的拙见,大多数时候,模拟 IConfiguration 会突出代码设计缺陷。
您应该尽可能依赖选项模式来提供对部分配置的强类型访问。如果您的应用程序配置错误(而不是在运行时执行代码读取 IConfiguration 时),它还会简化测试并使您的代码在启动期间失败。
如果你真的(真的)需要模拟它,那么我建议不要模拟它,而是按照@Nkosi
中解释的内存配置来伪造它
虽然 Nkosi 的答案非常适合简单的结构,但有时您希望能够拥有更复杂的对象(如数组)而无需重复整个部分路径,并且能够自己使用预期的类型。如果您不太关心性能(您应该在单元测试中吗?)那么这种扩展方法可能会有所帮助。
public static void AddObject(this IConfigurationBuilder cb, object model) {
cb.AddJsonStream(new MemoryStream(Encoding.UTF8.GetString(JsonConvert.SerializeObject(model))));
}
然后像这样使用
IConfiguration configuration = new ConfigurationBuilder()
.AddObject(new {
SectionName = myObject
})
.Build();
使用 SetupGet 方法模拟配置值和 return 任何字符串。
var configuration = new Mock<IConfiguration>();
configuration.SetupGet(x => x[It.IsAny<string>()]).Returns("the string you want to return");
我们需要模拟 IConfiguration.GetSection 在 GetValue 扩展方法中执行的 wichs。
您注入 IConfiguration:
private readonly Mock<IConfiguration> _configuration;
和//排列部分中的:
_configuration.Setup(c => c.GetSection(It.IsAny())).Returns(new Mock().Object);
它对我来说就像一个魅力。
我发现这个解决方案对我的 XUnit C# 测试和相应的 C# 代码很可靠:
在控制器中
string authConnection = this._config["Keys:AuthApi"] + "/somePathHere/Tokens/jwt";
在 XUnit 测试中
Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();
mockConfig.SetupGet(x => x[It.Is<string>(s => s == "Keys:AuthApi")]).Returns("some path here");
我试图模拟顶级(不属于任何部分)配置值(.NET Core 的 IConfiguration),但没有成功。例如,这些都不起作用(使用 NSubstitute,但它与 Moq 或我认为的任何模拟包相同):
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
在我的例子中,我还需要从同一个配置实例调用 GetSection
。
IConfiguration.GetSection<T>
必须间接模拟。我不完全理解为什么因为 NSubstitute,如果我理解正确的话,创建它自己的你正在动态模拟的接口的实现(在内存程序集中)。但这似乎是唯一可以做到的方法。包括顶级部分和常规部分。
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");
您可以使用内存中数据的实际配置实例。
//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();
//...
现在就是根据需要使用配置来练习测试了
//...
string value = configuration.GetValue<string>("TopLevelKey");
string sectionValue = configuration.GetSection("SectionName").GetValue<string>("SomeKey");
//...
我不知道 NSubstitute,但这就是我们在 Moq 中可以做的。 在这两种情况下,方法都是相同的。
您可以模拟 GetSection
和 return 您自己的 IConfigurationSection
。
这包括两个步骤。
1).为 IConfigurationSection
(mockSection)创建模拟并设置 .Value
属性 到 return 你想要的配置值。
2).在 Mock 上 Mock .GetSection
,并且 return 以上 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(It.Is<string>(k=>k=="ConfigKey"))).Returns(mockSection.Object);
GetValue<T>()
内部使用 GetSection()
.
模拟 IConfiguration
Mock<IConfiguration> config = new Mock<IConfiguration>();
设置获取
config.SetupGet(x => x[It.Is<string>(s => s == "DeviceTelemetryContainer")]).Returns("testConatiner");
config.SetupGet(x => x[It.Is<string>(s => s == "ValidPowerStatus")]).Returns("On");
我可以想象在一些罕见的情况下需要它,但以我的拙见,大多数时候,模拟 IConfiguration 会突出代码设计缺陷。
您应该尽可能依赖选项模式来提供对部分配置的强类型访问。如果您的应用程序配置错误(而不是在运行时执行代码读取 IConfiguration 时),它还会简化测试并使您的代码在启动期间失败。
如果你真的(真的)需要模拟它,那么我建议不要模拟它,而是按照@Nkosi
虽然 Nkosi 的答案非常适合简单的结构,但有时您希望能够拥有更复杂的对象(如数组)而无需重复整个部分路径,并且能够自己使用预期的类型。如果您不太关心性能(您应该在单元测试中吗?)那么这种扩展方法可能会有所帮助。
public static void AddObject(this IConfigurationBuilder cb, object model) {
cb.AddJsonStream(new MemoryStream(Encoding.UTF8.GetString(JsonConvert.SerializeObject(model))));
}
然后像这样使用
IConfiguration configuration = new ConfigurationBuilder()
.AddObject(new {
SectionName = myObject
})
.Build();
使用 SetupGet 方法模拟配置值和 return 任何字符串。
var configuration = new Mock<IConfiguration>();
configuration.SetupGet(x => x[It.IsAny<string>()]).Returns("the string you want to return");
我们需要模拟 IConfiguration.GetSection 在 GetValue 扩展方法中执行的 wichs。
您注入 IConfiguration:
private readonly Mock<IConfiguration> _configuration;
和//排列部分中的:
_configuration.Setup(c => c.GetSection(It.IsAny())).Returns(new Mock().Object);
它对我来说就像一个魅力。
我发现这个解决方案对我的 XUnit C# 测试和相应的 C# 代码很可靠:
在控制器中
string authConnection = this._config["Keys:AuthApi"] + "/somePathHere/Tokens/jwt";
在 XUnit 测试中
Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();
mockConfig.SetupGet(x => x[It.Is<string>(s => s == "Keys:AuthApi")]).Returns("some path here");