Comment se moquer de ConfigurationManager.AppSettings avec moq

124

Je suis coincé à ce point de code dont je ne sais pas comment me moquer:

ConfigurationManager.AppSettings["User"];

Je dois me moquer du ConfigurationManager, mais je n'en ai pas la moindre idée, j'utilise Moq .

Quelqu'un peut me donner un conseil? Merci!

Otuyh
la source

Réponses:

105

Je pense qu'une approche standard consiste à utiliser un modèle de façade pour envelopper le gestionnaire de configuration, puis vous avez quelque chose de faiblement couplé sur lequel vous avez le contrôle.

Donc, vous envelopperiez le ConfigurationManager. Quelque chose comme:

public class Configuration: IConfiguration
{
    public User
    {
        get
        { 
            return ConfigurationManager.AppSettings["User"];
        }
    }
}

(Vous pouvez simplement extraire une interface de votre classe de configuration, puis utiliser cette interface partout dans votre code) Ensuite, vous vous moquez simplement de l'IConfiguration. Vous pourrez peut-être implémenter la façade elle-même de différentes manières. Ci-dessus, j'ai choisi simplement d'envelopper les propriétés individuelles. Vous obtenez également l'avantage de disposer d'informations fortement typées avec lesquelles travailler plutôt que de tableaux de hachage faiblement typés.

Joshua Enfield
la source
6
C'est ce que je fais conceptuellement aussi. Cependant, j'utilise Castle DictionaryAdapter (qui fait partie de Castle Core) qui génère l'implémentation de l'interface à la volée. J'ai écrit à ce sujet il y a quelque temps: blog.andreloker.de/post/2008/09/05/… ( faites défiler jusqu'à "Une solution" pour voir comment j'utilise Castle DictionaryAdapter)
Andre Loker
C'est chouette et c'est un bon article. Je devrai garder cela à l'esprit pour l'avenir.
Joshua Enfield
Je pourrais également ajouter - en fonction de votre purisme et de vos interprétations - cela pourrait à la place ou aussi être appelé un mandataire ou un adaptateur de délégué.
Joshua Enfield
3
D'en haut, il utilise simplement Moq comme "normal". Non testé, mais quelque chose comme: var configurationMock = new Mock<IConfiguration>();et pour la configuration:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
Joshua Enfield
Ce scénario est utilisé lorsqu'une couche dépend de IConfiguration et que vous devez simuler IConfiguration, mais comment testeriez-vous l'implémentation d'IConfiguration? Et si vous appelez dans un test unitaire ConfigurationManager.AppSettings ["User"], cela ne testera pas l'unité, mais testera les valeurs extraites du fichier de configuration, qui n'est pas un test unitaire. Si vous avez besoin de vérifier l'implémentation, voir @ zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov
174

J'utilise AspnetMvc4. Il y a un instant j'ai écrit

ConfigurationManager.AppSettings["mykey"] = "myvalue";

dans ma méthode de test et cela a parfaitement fonctionné.

Explication: la méthode de test s'exécute dans un contexte avec des paramètres d'application tirés de, généralement un web.configou myapp.config. ConfigurationsManagerpeut atteindre cet objet global d'application et le manipuler.

Cependant: si vous avez un lanceur de tests exécutant des tests en parallèle, ce n'est pas une bonne idée.

LosManos
la source
8
C'est un moyen vraiment intelligent et simple de résoudre le problème! Bravo pour la simplicité!
Navap du
1
Beaucoup plus facile que de créer une abstraction dans la plupart des cas
Michael Clark
2
C'est tout???? L'éclat est dans la simplicité alors que je me demandais comment tester cette classe scellée particulière.
Piotr Kula
6
ConfigurationManager.AppSettingsest un NameValueCollectionqui n'est pas thread-safe, donc des tests parallèles en l'utilisant sans synchronisation appropriée ne sont de toute façon pas une bonne idée. Sinon, vous pouvez simplement appeler ConfigurationManager.AppSettings.Clear()votre TestInitialize/ ctor et vous êtes en or.
Ohad Schneider
1
Simple et concis. De loin la meilleure réponse!
znn
21

Ce n'est peut-être pas ce que vous devez accomplir, mais avez-vous envisagé d'utiliser un app.config dans votre projet de test? Ainsi, le ConfigurationManager obtiendra les valeurs que vous avez mises dans app.config et vous n'avez pas besoin de vous moquer de quoi que ce soit. Cette solution fonctionne bien pour mes besoins, car je n'ai jamais besoin de tester un fichier de configuration "variable".

Iridio
la source
7
Si le comportement du code testé change en fonction de la valeur d'une valeur de configuration, il est certainement plus facile de le tester s'il ne dépend pas directement d'AppSettings.
Andre Loker
2
C'est une mauvaise pratique car vous ne testez jamais d'autres paramètres possibles. La réponse de Joshua Enfield est idéale pour les tests.
mkaj
4
Alors que d'autres sont contre cette réponse, je dirais que leur position est un peu généralisée. C'est une réponse très valable dans certains scénarios et cela dépend vraiment de vos besoins. Par exemple, disons que j'ai 4 clusters différents, chacun ayant une URL de base différente. Ces 4 clusters sont extraits, lors de l'exécution, du Web.configprojet englobant. Pendant les tests, extraire des valeurs bien connues de l ' app.configest très valide. Le test unitaire a juste besoin de s'assurer que les conditions quand il tire disent que "cluster1" fonctionne; il n'y a que 4 clusters différents dans ce cas.
Mike Perrenoud
14

Vous pouvez utiliser des shims pour modifier AppSettingsun NameValueCollectionobjet personnalisé . Voici un exemple de la façon dont vous pouvez y parvenir:

[TestMethod]
public void TestSomething()
{
    using(ShimsContext.Create()) {
        const string key = "key";
        const string value = "value";
        ShimConfigurationManager.AppSettingsGet = () =>
        {
            NameValueCollection nameValueCollection = new NameValueCollection();
            nameValueCollection.Add(key, value);
            return nameValueCollection;
        };

        ///
        // Test code here.
        ///

        // Validation code goes here.        
    }
}

Vous pouvez en savoir plus sur les shims et les faux à, Isoler le code en cours de test avec Microsoft Fakes . J'espère que cela t'aides.

Zorayr
la source
6
L'auteur demande comment faire avec moq, pas sur MS Fakes.
JPCF
6
Et en quoi est-ce différent? Il réussit à se moquer en supprimant la dépendance aux données de son code. Utiliser C # Fakes est une approche!
Zorayr
9

Avez-vous envisagé de stubbing au lieu de vous moquer? La AppSettingspropriété est un NameValueCollection:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Arrange
        var settings = new NameValueCollection {{"User", "Otuyh"}};
        var classUnderTest = new ClassUnderTest(settings);

        // Act
        classUnderTest.MethodUnderTest();

        // Assert something...
    }
}

public class ClassUnderTest
{
    private readonly NameValueCollection _settings;

    public ClassUnderTest(NameValueCollection settings)
    {
        _settings = settings;
    }

    public void MethodUnderTest()
    {
        // get the User from Settings
        string user = _settings["User"];

        // log
        Trace.TraceInformation("User = \"{0}\"", user);

        // do something else...
    }
}

Les avantages sont une implémentation plus simple et aucune dépendance à System.Configuration jusqu'à ce que vous en ayez vraiment besoin.

DanielLarsenNZ
la source
3
J'aime le mieux cette approche. D'une part, envelopper le gestionnaire de configuration avec un IConfigurationcomme le suggère Joshua Enfield peut être de trop haut niveau, et vous pourriez manquer des bogues qui existent en raison de choses comme une mauvaise analyse de la valeur de configuration. D'un autre côté, utiliser ConfigurationManager.AppSettingsdirectement comme le suggère LosManos est trop un détail d'implémentation, sans parler qu'il peut avoir des effets secondaires sur d'autres tests et ne peut pas être utilisé dans des tests parallèles sans synchronisation manuelle (ce qui NameValueConnectionn'est pas thread-safe).
Ohad Schneider
2

Il s'agit d'une propriété statique et Moq est conçu pour les méthodes ou classes d'instance Moq qui peuvent être simulées via l'héritage. En d'autres termes, Moq ne vous sera d'aucune utilité ici.

Pour moquer la statique, j'utilise un outil appelé Moles , qui est gratuit. Il existe d'autres outils d'isolation de framework, comme Typemock, qui peuvent également le faire, même si je pense que ce sont des outils payants.

En ce qui concerne la statique et les tests, une autre option consiste à créer vous-même l'état statique, bien que cela puisse souvent être problématique (comme, j'imagine que ce serait dans votre cas).

Et, enfin, si les frameworks d'isolation ne sont pas une option et que vous êtes engagé dans cette approche, la façade mentionnée par Joshua est une bonne approche, ou toute approche en général où vous factorisez le code client de ceci loin de la logique métier que vous utilisez pour tester.

Erik Dietrich
la source
1

Je pense que l'écriture de votre propre fournisseur app.config est une tâche simple et est plus utile que toute autre chose. Surtout, vous devriez éviter les faux comme les cales, etc. car dès que vous les utilisez, Edit & Continue ne fonctionne plus.

Les fournisseurs que j'utilise ressemblent à ceci:

Par défaut, ils obtiennent les valeurs du App.configmais pour les tests unitaires, je peux remplacer toutes les valeurs et les utiliser indépendamment dans chaque test.

Il n'y a pas besoin d'interfaces ou de les implémenter à chaque fois encore et encore. J'ai une DLL d'utilitaires et j'utilise ce petit assistant dans de nombreux projets et tests unitaires.

public class AppConfigProvider
{
    public AppConfigProvider()
    {
        ConnectionStrings = new ConnectionStringsProvider();
        AppSettings = new AppSettingsProvider();
    }

    public ConnectionStringsProvider ConnectionStrings { get; private set; }

    public AppSettingsProvider AppSettings { get; private set; }
}

public class ConnectionStringsProvider
{
    private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    public string this[string key]
    {
        get
        {
            string customValue;
            if (_customValues.TryGetValue(key, out customValue))
            {
                return customValue;
            }

            var connectionStringSettings = ConfigurationManager.ConnectionStrings[key];
            return connectionStringSettings == null ? null : connectionStringSettings.ConnectionString;
        }
    }

    public Dictionary<string, string> CustomValues { get { return _customValues; } }
}

public class AppSettingsProvider
{
    private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    public string this[string key]
    {
        get
        {
            string customValue;
            return _customValues.TryGetValue(key, out customValue) ? customValue : ConfigurationManager.AppSettings[key];
        }
    }

    public Dictionary<string, string> CustomValues { get { return _customValues; } }
}
t3chb0t
la source
1

Que diriez-vous simplement de définir ce dont vous avez besoin? Parce que je ne veux pas me moquer de .NET, est-ce que je ...?

System.Configuration.ConfigurationManager.AppSettings["myKey"] = "myVal";

Vous devriez probablement nettoyer les AppSettings au préalable pour vous assurer que l'application ne voit que ce que vous voulez.

Eike
la source