So verspotten Sie ConfigurationManager.AppSettings mit moq

122

Ich stecke an diesem Punkt des Codes fest, den ich nicht verspotten kann:

ConfigurationManager.AppSettings["User"];

Ich muss den ConfigurationManager verspotten, aber ich habe keine Ahnung, ich benutze Moq .

Kann mir jemand einen Tipp geben? Vielen Dank!

Otuyh
quelle

Antworten:

103

Ich glaube, ein Standardansatz besteht darin , den Konfigurationsmanager mit einem Fassadenmuster zu umhüllen, und dann haben Sie etwas lose gekoppeltes, über das Sie die Kontrolle haben.

Sie würden also den ConfigurationManager einpacken. Etwas wie:

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

(Sie können einfach eine Schnittstelle aus Ihrer Konfigurationsklasse extrahieren und diese Schnittstelle dann überall in Ihrem Code verwenden.) Dann verspotten Sie einfach die IConfiguration. Möglicherweise können Sie die Fassade selbst auf verschiedene Arten implementieren. Oben habe ich mich entschieden, nur die einzelnen Eigenschaften zu verpacken. Sie erhalten auch den Nebeneffekt, dass Sie stark typisierte Informationen für die Arbeit mit schwach typisierten Hash-Arrays haben.

Joshua Enfield
quelle
6
Das mache ich auch konzeptionell. Ich verwende jedoch Castle DictionaryAdapter (Teil von Castle Core), das die Implementierung der Schnittstelle im laufenden Betrieb generiert. Ich habe vor einiger Zeit darüber geschrieben: blog.andreloker.de/post/2008/09/05/… (scrollen Sie nach unten zu "Eine Lösung", um zu sehen, wie ich Castle DictionaryAdapter verwende)
Andre Loker
Das ist geschickt und das ist ein guter Artikel. Ich muss das für die Zukunft berücksichtigen.
Joshua Enfield
Ich könnte auch hinzufügen - abhängig von Ihrem Purismus und Ihren Interpretationen - dies könnte stattdessen oder auch als Delegate Proxy oder Adapter bezeichnet werden.
Joshua Enfield
3
Von oben wird Moq nur als "normal" verwendet. Ungetestet, aber so etwas wie: var configurationMock = new Mock<IConfiguration>();und für das Setup:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
Joshua Enfield
Dieses Szenario wird verwendet, wenn eine Schicht von der IConfiguration abhängig ist und Sie die IConfiguration verspotten müssen. Wie würden Sie jedoch die IConfiguration-Implementierung testen? Wenn Sie einen Unit Test ConfigurationManager.AppSettings ["User"] aufrufen, wird das Gerät nicht getestet, sondern getestet, welche Werte aus der Konfigurationsdatei abgerufen werden. Dies ist kein Unit-Test. Wenn Sie die Implementierung überprüfen müssen, finden Sie unter @ zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov
172

Ich benutze AspnetMvc4. Vor einem Moment habe ich geschrieben

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

in meiner Testmethode und es hat perfekt funktioniert.

Erläuterung: Die Testmethode wird in einem Kontext ausgeführt, in dem App-Einstellungen verwendet werden, normalerweise a web.configoder myapp.config. ConfigurationsManagerkann dieses anwendungsglobale Objekt erreichen und es manipulieren.

Allerdings: Wenn Sie einen Testläufer haben, der parallel Tests durchführt, ist dies keine gute Idee.

LosManos
quelle
8
Dies ist eine wirklich clevere und einfache Möglichkeit, das Problem zu lösen! Ein großes Lob für die Einfachheit!
Navap
1
In den meisten Fällen viel einfacher als das Erstellen einer Abstraktion
Michael Clark
2
Das ist es???? Die Brillanz liegt in der Einfachheit, als ich mir den Kopf zerbrochen habe, wie man diese bestimmte versiegelte Klasse testet.
Piotr Kula
5
ConfigurationManager.AppSettingsist eine, NameValueCollectiondie nicht threadsicher ist, daher sind parallele Tests, die sie ohne ordnungsgemäße Synchronisation verwenden, sowieso keine gute Idee. Andernfalls können Sie einfach ConfigurationManager.AppSettings.Clear()Ihren TestInitialize/ ctor anrufen und Sie sind golden.
Ohad Schneider
1
Einfach und prägnant. Mit Abstand die beste Antwort!
znn
21

Vielleicht ist es nicht das, was Sie erreichen müssen, aber haben Sie darüber nachgedacht, eine app.config in Ihrem Testprojekt zu verwenden? Der ConfigurationManager erhält also die Werte, die Sie in die app.config eingegeben haben, und Sie müssen nichts verspotten. Diese Lösung funktioniert gut für meine Bedürfnisse, da ich nie eine "variable" Konfigurationsdatei testen muss.

Iridio
quelle
7
Wenn sich das Verhalten des zu testenden Codes in Abhängigkeit vom Wert eines Konfigurationswerts ändert, ist es sicherlich einfacher, ihn zu testen, wenn er nicht direkt von AppSettings abhängt.
Andre Loker
2
Dies ist eine schlechte Vorgehensweise, da Sie niemals andere mögliche Einstellungen testen. Die Antwort von Joshua Enfield eignet sich hervorragend zum Testen.
mkaj
4
Während andere gegen diese Antwort sind, würde ich sagen, dass ihre Position etwas verallgemeinert ist. Dies ist in einigen Szenarien eine sehr gültige Antwort und hängt wirklich nur davon ab, was Sie brauchen. Angenommen, ich habe 4 verschiedene Cluster mit jeweils einer anderen Basis-URL. Diese 4 Cluster werden zur Laufzeit aus Web.configdem Projekt gezogen. Während des Tests app.configist es sehr gültig , einige bekannte Werte aus dem zu ziehen . Der Komponententest muss nur sicherstellen, dass die Bedingungen beim Ziehen von "cluster1" funktionieren. In diesem Fall gibt es immer nur 4 verschiedene Cluster.
Mike Perrenoud
14

Sie können Shims verwenden, um Änderungen AppSettingsan einem benutzerdefinierten NameValueCollectionObjekt vorzunehmen . Hier ist ein Beispiel, wie Sie dies erreichen können:

[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.        
    }
}

Weitere Informationen zu Shims und Fakes finden Sie unter Isolieren des zu testenden Codes mit Microsoft Fakes . Hoffe das hilft.

Zorayr
quelle
6
Der Autor bittet um eine Anleitung mit moq, nicht um MS Fakes.
JPCF
6
Und wie ist das anders? Es wird verspottet, indem die Datenabhängigkeit aus seinem Code entfernt wird. Die Verwendung von C # Fakes ist ein Ansatz!
Zorayr
9

Haben Sie darüber nachgedacht, statt zu verspotten? Die AppSettingsEigenschaft ist ein 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...
    }
}

Die Vorteile sind eine einfachere Implementierung und keine Abhängigkeit von System.Configuration, bis Sie es wirklich brauchen.

DanielLarsenNZ
quelle
3
Ich mag diesen Ansatz am besten. Auf der einen Seite kann das Umschließen des Konfigurationsmanagers mit einem, IConfigurationwie Joshua Enfield vorschlägt, zu hoch sein, und Sie könnten Fehler übersehen, die aufgrund von Dingen wie schlechtem Parsen von Konfigurationswerten auftreten. Auf der anderen Seite ist die ConfigurationManager.AppSettingsdirekte Verwendung, wie LosManos vorschlägt, ein zu großes Implementierungsdetail, ganz zu schweigen davon, dass sie Nebenwirkungen auf andere Tests haben kann und nicht in parallelen Testläufen ohne manuelle Synchronisierung verwendet werden kann (da dies NameValueConnectionnicht threadsicher ist).
Ohad Schneider
2

Dies ist eine statische Eigenschaft, und Moq wurde für Moq-Instanzmethoden oder -Klassen entwickelt, die über die Vererbung verspottet werden können. Mit anderen Worten, Moq wird Ihnen hier keine Hilfe sein.

Zum Verspotten von Statiken verwende ich ein Tool namens Moles , das kostenlos ist. Es gibt andere Framework-Isolationstools wie Typemock, die dies ebenfalls können, obwohl ich glaube, dass dies kostenpflichtige Tools sind.

Wenn es um Statik und Tests geht, besteht eine andere Möglichkeit darin, den statischen Zustand selbst zu erstellen, obwohl dies oft problematisch sein kann (wie ich mir vorstellen würde, wäre dies in Ihrem Fall der Fall).

Und schließlich, wenn die Isolations-Frameworks keine Option sind und Sie sich diesem Ansatz verpflichtet fühlen, ist die von Joshua erwähnte Fassade ein guter Ansatz oder ein Ansatz im Allgemeinen, bei dem Sie den Client-Code davon von der Geschäftslogik abgrenzen, die Sie verwenden Ich benutze zum Testen.

Erik Dietrich
quelle
1

Ich denke, das Schreiben eines eigenen app.config-Anbieters ist eine einfache Aufgabe und nützlicher als alles andere. Insbesondere sollten Sie Fälschungen wie Unterlegscheiben usw. vermeiden, da Edit & Continue nicht mehr funktioniert, sobald Sie sie verwenden.

Die Anbieter, die ich benutze, sehen folgendermaßen aus:

Standardmäßig erhalten sie die Werte von, App.configaber für Komponententests kann ich alle Werte überschreiben und sie in jedem Test unabhängig verwenden.

Es sind keine Schnittstellen erforderlich oder werden jedes Mal implementiert. Ich habe eine Dienstprogramm-DLL und verwende diesen kleinen Helfer in vielen Projekten und Unit-Tests.

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
quelle
1

Wie wäre es einfach einzustellen, was Sie brauchen? Weil ich .NET nicht verspotten will, oder ...?

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

Sie sollten die AppSettings wahrscheinlich vorher bereinigen, um sicherzustellen, dass die App nur das sieht, was Sie möchten.

Eike
quelle