C # Create Mock Configuration.GetSection («Section: SubSection») pour la liste d'objets

Sep 01 2020

Objectif

Créez un objet simulé, en utilisant Moq et XUnit, pour charger la section spécifique "Caractère / Compétences" afin d'améliorer la couverture des tests unitaires.

Le SUT (à un moment donné), charge le réglage de la manière

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

À partir de l'application suivante

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

Lecture précédente

En lisant ces articles (et d'autres, à la suite de Google), j'ai remarqué que nous ne pouvons utiliser qu'un type de données primitif "string" ou encore un nouvel objet Mock <IConfigurationSection> (sans paramètre):

  • Stack Overflow - comment se moquer de Configuration.GetSection («foo: bar») ,
  • Méthode d'extension IConfiguration moqueuse
  • Mocking IConfiguration Méthode d'extension Getvalue () dans le test unitaire

Contrainte: Copier le fichier appSetting dans le TestProject (ou créer un MemoryStream) pour charger les paramètres réels pourrait résoudre ce scénario, mais le test serait une "Intégration" au lieu de "Unit"; car il existe une dépendance d'E / S.

L'approche

L'idée du code (montrée plus tard) se moque de chaque propriété (clé / id) puis les fusionne dans un arbre similaire à celui-ci:

  • "Caractère" ------ Configuration à lire, en utilisant GetSection()puisGet<T>()
    • "Skills" ------ Liste de configuration avec attribut fusionné
      • "Key" - "CQC Combat" ------ Valeur primitive 1
      • "Id" - "15465" ------ Valeur primitive 2

Le code

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

Résultat attendu

En exécutant le système d'origine, j'obtiens la liste instanciée avec son respectif Voici la capture d'écran:

Résultat fictif

Le code ci-dessus, je n'obtiens que la liste instanciée mais tous les attributs retournent null. Voici la capture d'écran:

Réponses

1 CesarUrdaneta Sep 03 2020 at 00:56

Enfin, j'ai refactoré le code, en supprimant tout le foreachbloc et en remplaçant l'initialisation de la liste var mockConfSections = new List<IConfigurationSection>();par le morceau de code suivant, qui est plus simple et plus propre.

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

Explication

Comme l'implémentation précédente a construit un arbre de configuration avec des nœuds simulés, il était nécessaire de créer une configuration et de renvoyer pour chacun d'eux, ce qui a abouti à une solution gonflée.

Sur la base de l'article Garder les paramètres de configuration en mémoire , j'ai projeté la liste avec un dictionnaire clé / ID aplati à l'aide de LINQ SelectMany , puis j'ai construit la configuration de la mémoire et finalement moqué le paramètre avec des «nœuds réels», ce qui a abouti à une configuration simulée.