Come simulare ConfigurationManager.AppSettings con moq


124

Sono bloccato a questo punto del codice che non so come deridere:

ConfigurationManager.AppSettings["User"];

Devo prendere in giro il ConfigurationManager, ma non ne ho la più pallida idea, sto usando Moq .

Qualcuno può darmi un suggerimento? Grazie!

Risposte:


105

Credo che un approccio standard a questo sia quello di utilizzare un modello di facciata per avvolgere il gestore di configurazione e quindi si dispone di qualcosa di liberamente accoppiato su cui si ha il controllo.

Quindi avvolgerai il ConfigurationManager. Qualcosa di simile a:

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

(Puoi semplicemente estrarre un'interfaccia dalla tua classe di configurazione e quindi usare quell'interfaccia ovunque nel tuo codice) Quindi prendi in giro IConfiguration. Potresti essere in grado di implementare la facciata stessa in diversi modi. Sopra ho scelto solo di racchiudere le singole proprietà. Si ottiene anche il vantaggio collaterale di avere informazioni fortemente tipizzate con cui lavorare piuttosto che array di hash di tipo debole.


6
Questo è concettualmente anche quello che sto facendo. Tuttavia, utilizzo Castle DictionaryAdapter (parte di Castle Core) che genera l'implementazione dell'interfaccia al volo. Ne ho scritto qualche tempo fa: blog.andreloker.de/post/2008/09/05/… (scorri verso il basso fino a "A Solution" per vedere come uso Castle DictionaryAdapter)
Andre Loker

Questo è elegante e questo è un buon articolo. Dovrò tenerlo a mente per il futuro.
Joshua Enfield,

Potrei anche aggiungere - a seconda del tuo purismo e delle tue interpretazioni - questo potrebbe invece essere chiamato anche Delegate Proxy o Adapter.
Joshua Enfield

3
Dall'alto sta semplicemente usando Moq come "normale". Non testato, ma qualcosa del tipo: var configurationMock = new Mock<IConfiguration>();e per la configurazione:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
Joshua Enfield

Questo scenario viene utilizzato quando un livello dipende da IConfiguration ed è necessario simulare l'IConfiguration, ma come testeresti l'implementazione di IConfiguration? E se si richiama uno Unit Test ConfigurationManager.AppSettings ["Utente"], questo non testerebbe l'unità, ma testerebbe quali valori vengono recuperati dal file di configurazione, che non è uno unit test. Se è necessario verificare l'implementazione, vedere @ zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov

174

Sto usando AspnetMvc4. Un attimo fa ho scritto

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

nel mio metodo di prova e ha funzionato perfettamente.

Spiegazione: il metodo di test viene eseguito in un contesto con le impostazioni dell'app prese da, in genere un web.configo myapp.config. ConfigurationsManagerpuò raggiungere questo oggetto globale dell'applicazione e manipolarlo.

Tuttavia: se hai un test runner che esegue i test in parallelo, questa non è una buona idea.


8
Questo è davvero un modo semplice e intelligente per risolvere il problema! Complimenti per la semplicità!
Navap

1
Molto più facile che creare un'astrazione nella maggior parte dei casi
Michael Clark,

2
Questo è tutto???? La genialità sta nella semplicità mentre mi arrovellavo il cervello su come testare questa particolare classe sigillata.
Piotr Kula

6
ConfigurationManager.AppSettingsè un programma NameValueCollectionche non è thread-safe, quindi i test paralleli che lo usano senza una corretta sincronizzazione non sono comunque una buona idea. Altrimenti puoi semplicemente chiamare il ConfigurationManager.AppSettings.Clear()tuo TestInitialize/ ctor e sei d'oro.
Ohad Schneider

1
Semplice e conciso. Di gran lunga la migliore risposta!
znn

21

Forse non è quello che devi realizzare, ma hai considerato di utilizzare un app.config nel tuo progetto di test? Quindi il ConfigurationManager otterrà i valori che hai inserito in app.config e non devi deridere nulla. Questa soluzione funziona bene per le mie esigenze, perché non ho mai bisogno di testare un file di configurazione "variabile".


7
Se il comportamento del codice sottoposto a test cambia a seconda del valore di un valore di configurazione, è sicuramente più facile testarlo se non dipende direttamente da AppSettings.
Andre Loker,

2
Questa è una cattiva pratica poiché non stai mai testando altre possibili impostazioni. La risposta di Joshua Enfield è ottima per i test.
mkaj

4
Mentre altri sono contrari a questa risposta, direi che la loro posizione è un po 'generalizzata. Questa è una risposta molto valida in alcuni scenari e dipende solo da ciò di cui hai bisogno. Ad esempio, supponiamo di avere 4 cluster diversi, ciascuno con un URL di base diverso. Questi 4 cluster vengono estratti, durante il runtime, dall'inclusione Web.configdel progetto. Durante il test, estrarre alcuni valori ben noti da app.configè molto valido. Lo unit test deve solo assicurarsi che le condizioni su quando viene eseguito il pull dicano "cluster1" funziona; ci sono sempre e solo 4 diversi cluster in questo caso.
Mike Perrenoud

14

È possibile utilizzare gli spessori per modificare AppSettingsun NameValueCollectionoggetto personalizzato . Ecco un esempio di come puoi ottenere questo risultato:

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

Puoi leggere ulteriori informazioni su shim e falsi su Isolamento del codice sottoposto a test con Microsoft Fakes . Spero che questo ti aiuti.


6
L'autore sta chiedendo come fare con moq, non su MS Fakes.
JPCF

6
E in che modo è diverso? Ottiene beffa rimuovendo la dipendenza dai dati dal suo codice. L'utilizzo di C # Fakes è un approccio!
Zorayr

9

Hai preso in considerazione lo stubbing invece di deridere? La AppSettingsproprietà è una 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...
    }
}

I vantaggi sono un'implementazione più semplice e nessuna dipendenza da System.Configuration fino a quando non ne hai veramente bisogno.


3
Mi piace di più questo approccio. Da un lato, avvolgere il gestore di configurazione con un, IConfigurationcome suggerisce Joshua Enfield, può essere di livello troppo alto e potresti perdere bug che esistono a causa di cose come una cattiva analisi del valore di configurazione. D'altra parte, l'utilizzo ConfigurationManager.AppSettingsdiretto come suggerisce LosManos è un dettaglio di implementazione eccessivo, per non parlare del fatto che può avere effetti collaterali su altri test e non può essere utilizzato in esecuzioni di test parallele senza sincronizzazione manuale (in quanto NameValueConnectionnon è thread-safe).
Ohad Schneider

2

Questa è una proprietà statica e Moq è progettato per metodi o classi di istanza Moq che possono essere derisi tramite ereditarietà. In altre parole, Moq non ti sarà di alcun aiuto qui.

Per deridere la statica, utilizzo uno strumento chiamato Moles , che è gratuito. Ci sono altri strumenti di isolamento del framework, come Typemock che possono farlo anche se credo che siano strumenti a pagamento.

Quando si tratta di statica e test, un'altra opzione è creare lo stato statico da soli, anche se questo può spesso essere problematico (come, immagino che sarebbe nel tuo caso).

E, infine, se i framework di isolamento non sono un'opzione e sei impegnato in questo approccio, la facciata menzionata da Joshua è un buon approccio, o qualsiasi approccio in generale in cui prendi in considerazione il codice del cliente lontano dalla logica di business che tu stai usando per testare.


1

Penso che scrivere il tuo provider app.config sia un compito semplice ed è più utile di qualsiasi altra cosa. Soprattutto dovresti evitare falsi come spessori, ecc. Perché non appena li usi Modifica e continua non funziona più.

I provider che utilizzo hanno questo aspetto:

Per impostazione predefinita, ottengono i valori da App.configma per gli unit test posso sovrascrivere tutti i valori e utilizzarli in ogni test in modo indipendente.

Non c'è bisogno di alcuna interfaccia o implementarla ogni volta più e più volte. Ho una dll di utilità e utilizzo questo piccolo aiuto in molti progetti e unit test.

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

1

Che ne dici di impostare ciò di cui hai bisogno? Perché non voglio prendere in giro .NET, vero ...?

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

Probabilmente dovresti prima ripulire AppSettings per assicurarti che l'app veda solo ciò che desideri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.