C # Create Mock Configuration.GetSection (“ Section: SubSection”) สำหรับรายการวัตถุ

Sep 01 2020

วัตถุประสงค์

สร้างวัตถุจำลองโดยใช้ Moq และ XUnit สำหรับการโหลดส่วน "ตัวละคร / ทักษะ" เฉพาะเพื่อเพิ่มความครอบคลุมในการทดสอบหน่วย

SUT (ในบางประเด็น) โหลดการตั้งค่าในทาง

var skills = Configuration.GetSection(“Character:Skills”);

จาก appSetting ต่อไปนี้:

{
    "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..."
}

การอ่านก่อนหน้า

เมื่ออ่านโพสต์เหล่านี้ (และอื่น ๆ อันเป็นผลมาจาก Googling) ฉันสังเกตเห็นว่าเราสามารถใช้ประเภทข้อมูล "สตริง" แบบดั้งเดิมเท่านั้นหรือออบเจ็กต์Mock <IConfigurationSection> ใหม่ (โดยไม่มีการตั้งค่า):

  • Stack Overflow - วิธีล้อเลียน Configuration.GetSection (“ foo: bar”) ,
  • วิธีการจำลองส่วนขยาย IConfiguration
  • Mocking IConfiguration Getvalue () วิธีการขยายในการทดสอบหน่วย

ข้อ จำกัด : การคัดลอกไฟล์ appSetting ลงใน TestProject (หรือสร้าง MemoryStream) เพื่อโหลดการตั้งค่าจริงสามารถแก้สถานการณ์นี้ได้ แต่การทดสอบจะเป็น "การรวม" แทนที่จะเป็น "หน่วย"; เนื่องจากมีการพึ่งพา I / O

วิธีการ

แนวคิดของโค้ด (แสดงในภายหลัง) กำลังเยาะเย้ยคุณสมบัติแต่ละรายการ (คีย์ / id) จากนั้นรวมเข้ากับโครงสร้างที่คล้ายกับสิ่งนี้:

  • "ตัวละคร" ------ การกำหนดค่าที่จะอ่านใช้GetSection()แล้วGet<T>()
    • "ทักษะ" ------ รายการการกำหนดค่าพร้อมแอตทริบิวต์ที่ผสาน
      • "คีย์" - "CQC Combat" ------ ค่าดั้งเดิม 1
      • "Id" - "15465" ------ ค่าดั้งเดิม 2

รหัส

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

ผลลัพธ์ที่คาดหวัง

เรียกใช้ระบบดั้งเดิมฉันได้รับรายการอินสแตนซ์พร้อมกับตามลำดับนี่คือภาพหน้าจอ:

ผลลัพธ์ที่เยาะเย้ย

โค้ดด้านบนฉันได้รับเฉพาะรายการที่สร้างอินสแตนซ์ แต่แอตทริบิวต์ทั้งหมดกลับเป็นโมฆะ นี่คือภาพหน้าจอ:

คำตอบ

1 CesarUrdaneta Sep 03 2020 at 00:56

ในที่สุดฉันก็ปรับโครงสร้างโค้ดใหม่กำจัด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();

คำอธิบาย

เนื่องจากการใช้งานก่อนหน้านี้ได้สร้างแผนผังการกำหนดค่าที่มีโหนดจำลองจึงจำเป็นต้องสร้างการตั้งค่าและส่งคืนสำหรับแต่ละโหนดซึ่งส่งผลให้เกิดโซลูชันที่ป่อง

จากบทความการรักษาการตั้งค่าคอนฟิกในหน่วยความจำฉันคาดการณ์รายการด้วยพจนานุกรมคีย์ / รหัสแบบแบนโดยใช้LINQ SelectManyจากนั้นสร้างการกำหนดค่าหน่วยความจำและในที่สุดก็จำลองการตั้งค่าด้วย "โหนดจริง" ทำให้เกิดการตั้งค่าแบบจำลองหนึ่งรายการ