Como simular ConfigurationManager.AppSettings com moq

122

Estou preso neste ponto de código que não sei como zombar:

ConfigurationManager.AppSettings["User"];

Preciso zombar do ConfigurationManager, mas não tenho idéia, estou usando o Moq .

Alguém pode me dar uma dica? Obrigado!

Otuyh
fonte

Respostas:

103

Acredito que uma abordagem padrão para isso é usar um padrão de fachada para envolver o gerenciador de configuração e, em seguida, você terá algo fracamente acoplado sobre o qual tem controle.

Então você agruparia o ConfigurationManager. Algo como:

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

(Você pode apenas extrair uma interface da sua classe de configuração e, em seguida, usá-la em qualquer lugar do seu código). Em seguida, você apenas zomba da configuração ICon. Você poderá implementar a própria fachada de algumas maneiras diferentes. Acima, eu escolhi apenas envolver as propriedades individuais. Você também obtém o benefício colateral de ter informações fortemente digitadas para trabalhar com matrizes de hash fracamente digitadas.

Joshua Enfield
fonte
6
Conceitualmente, também estou fazendo isso. No entanto, eu uso o Castle DictionaryAdapter (parte do Castle Core) que gera a implementação da interface em tempo real. Eu escrevi sobre isso há algum tempo: blog.andreloker.de/post/2008/09/05/... (desça até "A Solução" para ver como eu uso Castelo DictionaryAdapter)
Andre Loker
Isso é bacana e é um bom artigo. Vou ter que ter isso em mente para o futuro.
Joshua Enfield
Eu também poderia adicionar - dependendo do seu purismo e interpretações - isso poderia ou também poderia ser chamado de proxy ou adaptador delegado.
Joshua Enfield
3
De cima, está apenas usando o Moq como "normal". Testado, mas algo como: var configurationMock = new Mock<IConfiguration>();e para a configuração:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
Joshua Enfield
Esse cenário é usado quando uma camada depende da configuração do ICon e você precisa zombar da configuração do ICon, mas como você testaria a implementação do IConfiguration? E se você chamar um UnitManager.AppSettings de teste de unidade ["Usuário"], isso não testará a unidade, mas testará quais valores são buscados no arquivo de configuração, o que não é um teste de unidade. Se você precisa verificar a implementação, consulte @ zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov
172

Estou usando o AspnetMvc4. Um momento atrás eu escrevi

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

no meu método de teste e funcionou perfeitamente.

Explicação: o método de teste é executado em um contexto com as configurações do aplicativo retiradas, normalmente a web.configou myapp.config. ConfigurationsManagerpode alcançar esse objeto global de aplicativo e manipulá-lo.

Porém: se você tem um executor de testes executando testes em paralelo, isso não é uma boa ideia.

LosManos
fonte
8
Esta é uma maneira realmente inteligente e simples de resolver o problema! Parabéns pela simplicidade!
Navap 14/10/16
1
Muito mais fácil do que criar uma abstração na maioria dos casos
Michael Clark
2
É isso aí???? O brilho está na simplicidade, pois eu estava tentando descobrir como testar essa aula selada em particular.
Piotr Kula
5
ConfigurationManager.AppSettingsé um NameValueCollectionque não é seguro para threads, portanto testes paralelos usando-o sem sincronização adequada não são uma boa ideia. Caso contrário, você pode simplesmente chamar ConfigurationManager.AppSettings.Clear()seu TestInitialize/ ctor e você estará dourado.
Ohad Schneider
1
Simples e conciso. De longe a melhor resposta!
znn 7/09/17
21

Talvez não seja o que você precisa realizar, mas você já pensou em usar um app.config em seu projeto de teste? Portanto, o ConfigurationManager obterá os valores que você coloca no app.config e você não precisa zombar de nada. Essa solução funciona bem para minhas necessidades, porque nunca preciso testar um arquivo de configuração "variável".

Iridio
fonte
7
Se o comportamento do código em teste for alterado, dependendo do valor de um valor de configuração, certamente será mais fácil testá-lo se não depender diretamente do AppSettings.
Andre Loker
2
Isso é uma prática ruim, pois você nunca está testando outras configurações possíveis. A resposta de Joshua Enfield é ótima para testes.
mkaj
4
Enquanto outros são contra essa resposta, eu diria que a posição deles é um pouco generalizada. Essa é uma resposta muito válida em alguns cenários e depende realmente do que você precisa. Por exemplo, digamos que eu tenha 4 clusters diferentes, cada um com um URL base diferente. Esses 4 clusters são retirados, durante o tempo de execução, do Web.configabrangendo o projeto. Durante o teste, extrair alguns valores conhecidos do app.configé muito válido. O teste de unidade só precisa garantir que as condições quando ele puxa, digamos "cluster1", funcionem; existem apenas 4 grupos diferentes neste caso.
Mike Perrenoud
14

Você pode usar calços para modificar AppSettingspara um NameValueCollectionobjeto personalizado . Aqui está um exemplo de como você pode conseguir isso:

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

Você pode ler mais sobre calços e falsificações em Isolando código sob teste com o Microsoft Fakes . Espero que isto ajude.

Zorayr
fonte
6
O autor está perguntando como fazer o moq, não sobre o MS Fakes.
JPCF 17/10
6
E como isso é diferente? Ele consegue zombar ao remover a dependência de dados do seu código. Usar C # Fakes é uma abordagem!
Zorayr #
9

Você já pensou em fazer stub em vez de zombar? A AppSettingspropriedade é uma 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...
    }
}

Os benefícios são uma implementação mais simples e nenhuma dependência do System.Configuration até que você realmente precise.

DanielLarsenNZ
fonte
3
Eu gosto dessa abordagem da melhor maneira. Por um lado, agrupar o gerenciador de configuração IConfigurationcomo Joshua Enfield sugere pode ser um nível muito alto e você pode perder bugs que existem devido a coisas como análise de valor de configuração incorreta. Por outro lado, o uso ConfigurationManager.AppSettingsdireto como LosManos sugere é um detalhe de implementação demais, sem mencionar que pode ter efeitos colaterais em outros testes e não pode ser usado em execuções de teste paralelas sem sincronização manual (como NameValueConnectionnão é seguro para threads).
Ohad Schneider
2

Essa é uma propriedade estática e o Moq é projetado para métodos ou classes de instância do Moq que podem ser zombados por herança. Em outras palavras, o Moq não ajudará você aqui.

Para zombar de estática, eu uso uma ferramenta chamada Moles , que é gratuita. Existem outras ferramentas de isolamento de estrutura, como o Typemock, que também podem fazer isso, embora eu acredite que sejam ferramentas pagas.

Quando se trata de estática e testes, outra opção é criar você mesmo o estado estático, embora isso possa ser problemático (como eu imagino que seria no seu caso).

E, finalmente, se as estruturas de isolamento não são uma opção e você está comprometido com essa abordagem, a fachada mencionada por Joshua é uma boa abordagem ou qualquer abordagem em geral em que você considere o código do cliente longe da lógica comercial que você usa. estou usando para testar.

Erik Dietrich
fonte
1

Eu acho que escrever o seu próprio provedor app.config é uma tarefa simples e é mais útil do que qualquer outra coisa. Especialmente, você deve evitar falsificações, como calços, etc., porque assim que usá-las, o Editar e Continuar não funciona mais.

Os provedores que uso são assim:

Por padrão, eles obtêm os valores dos App.configtestes de unidade, mas eu posso substituir todos os valores e usá-los em cada teste independentemente.

Não há necessidade de nenhuma interface ou implemente-a sempre. Eu tenho uma DLL de utilitários e uso esse pequeno auxiliar em muitos projetos e testes de unidade.

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

Que tal apenas definir o que você precisa? Porque, eu não quero zombar do .NET, quero ...?

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

Você provavelmente deve limpar o AppSettings de antemão para garantir que o aplicativo veja apenas o que deseja.

Eike
fonte