[英]C# how to mock Configuration.GetSection(“foo:bar”).Get<List<string>>()
[英]C# Create Mock Configuration.GetSection(“Section:SubSection”) for objects list
使用 Moq 和 XUnit 創建一個模擬對象,用於加載特定部分“字符/技能”以增強單元測試的覆蓋率。
SUT(在某些時候)以這種方式加載設置
var skills = Configuration.GetSection(“Character:Skills”);
從以下應用程序設置:
{
"dummyConfig1": {
"Description": "bla bla bla...",
},
"Character": {
"Name": "John Wick",
"Description": "A retired hitman seeking vengeance for the killing of the dog given to him...",
"Skills": [
{
"Key": "CQC Combat",
"Id": "15465"
},
{
"Key": "Firearms",
"Id": "14321"
},
{
"Key": "Stealth",
"Id": "09674"
},
{
"Key": "Speed",
"Id": "10203"
}
],
"DummyConf2": "more bla bla bla..."
}
閱讀這些帖子(和其他帖子,谷歌搜索的結果),我注意到我們只能使用原始的“字符串”數據類型或新的 Mock<IConfigurationSection>對象(沒有設置):
約束:將 appSetting 文件復制到 TestProject(或創建 MemoryStream)以加載真實設置可以解決這種情況,但測試將是“集成”而不是“單元”; 因為存在 I/O 依賴性。
代碼的想法(稍后顯示)是模擬每個屬性(鍵/ID),然后將它們合並到類似於以下的樹中:
GetSection()
然后Get<T>()
var skillsConfiguration = new List<SkillsConfig>
{
new SkillsConfig { Key = "CQC Combat" , Id = "15465" },
new SkillsConfig { Key = "Firearms" , Id = "14321" },
new SkillsConfig { Key = "Stealh" , Id = "09674" },
new SkillsConfig { Key = "Speed" , Id = "10203" },
};
var configurationMock = new Mock<IConfiguration>();
var mockConfSections = new List<IConfigurationSection>();
foreach (var skill in skillsConfiguration)
{
var index = skillsConfiguration.IndexOf(skill);
//Set the Key string value
var mockConfSectionKey = new Mock<IConfigurationSection>();
mockConfSectionKey.Setup(s => s.Path).Returns($"Character:Skills:{index}:Key");
mockConfSectionKey.Setup(s => s.Key).Returns("Key");
mockConfSectionKey.Setup(s => s.Value).Returns(skill.Key);
//Set the Id string value
var mockConfSectionId = new Mock<IConfigurationSection>();
mockConfSectionId.Setup(s => s.Path).Returns($"Character:Skills:{index}:Id");
mockConfSectionId.Setup(s => s.Key).Returns("Id");
mockConfSectionId.Setup(s => s.Value).Returns(skill.Id);
//Merge the attribute "key/id" as Configuration section list
var mockConfSection = new Mock<IConfigurationSection>();
mockConfSection.Setup(s => s.Path).Returns($"Character:Skills:{index}");
mockConfSection.Setup(s => s.Key).Returns(index.ToString());
mockConfSection.Setup(s => s.GetChildren()).Returns(new List<IConfigurationSection> { mockConfSectionKey.Object, mockConfSectionId.Object });
//Add the skill object with merged attributes
mockConfSections.Add(mockConfSection.Object);
}
// Add the Skill's list
var skillsMockSections = new Mock<IConfigurationSection>();
skillsMockSections.Setup(cfg => cfg.Path).Returns("Character:Skills");
skillsMockSections.Setup(cfg => cfg.Key).Returns("Skills");
skillsMockSections.Setup(cfg => cfg.GetChildren()).Returns(mockConfSections);
//Mock the whole section, for using GetSection() method withing SUT
configurationMock.Setup(cfg => cfg.GetSection("Character:Skills")).Returns(skillsMockSections.Object);
運行原始系統,我得到了各自的實例化列表 這是屏幕截圖:
上面的代碼,我只得到了實例化的列表,但所有的屬性都返回空值。 這是屏幕截圖:
最后我重構了代碼,去掉了整個foreach
塊並替換了列表初始化var mockConfSections = new List<IConfigurationSection>();
使用以下代碼,它更簡單,更干凈。
var fakeSkillSettings = skillsConfiguration.SelectMany(
skill => new Dictionary<string, string> {
{ $"Character:Skills:{skillsConfiguration.IndexOf(skill)}:Key", skill.Key },
{ $"Character:Skills:{skillsConfiguration.IndexOf(skill)}:Id" , skill.Id },
});
var configBuilder = new ConfigurationBuilder();
var mockConfSections = configBuilder.AddInMemoryCollection(fakeSkillSettings)
.Build()
.GetSection("Character:Skills")
.GetChildren();
由於之前的實現構建了帶有模擬節點的配置樹,因此需要為每個節點構建設置和返回,從而導致解決方案臃腫。
根據文章Keeping Configuration Settings in Memory ,我使用LINQ SelectMany用扁平的 Key/Id Dictionary 投影列表,然后構建內存配置,最后用“真實節點”模擬設置,從而產生一個模擬設置。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.