Como moq um método que tem um argumento opcional em sua assinatura sem especificá-lo explicitamente ou usar uma sobrecarga?

119

Dada a seguinte interface:

public interface IFoo
{
    bool Foo(string a, bool b = false);
}

Tentando zombar dele usando o Moq:

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>())).Returns(false);

fornece o seguinte erro no tempo de compilação:

Uma árvore de expressão pode não conter uma chamada ou invocação que use argumentos opcionais

Eu encontrei o problema acima levantado como um aprimoramento na lista de problemas do Moq e ele parece estar atribuído à versão 4.5 (sempre que houver).

Minha pergunta é: o que devo fazer, pois o que foi dito acima não será corrigido tão cedo? Minhas opções são apenas definir explicitamente o valor padrão do parâmetro opcional toda vez que eu o zoo (o que meio que anula o ponto de especificar um em primeiro lugar) ou criar uma sobrecarga sem o bool (como o que eu teria feito antes de C # 4)?

Ou alguém encontrou uma maneira mais inteligente de superar esse problema?

Appulus
fonte
5
Seria razoável especificar It.IsAny <bool> () para o segundo parâmetro?
Paul d'Aoust
Um ano e meio depois, isso ainda é verdade ..
Mukus
@Mukus, fique à vontade para soltar um PR, mano.
IamDOM

Respostas:

91

Acredito que sua única opção agora é incluir explicitamente o boolparâmetro na configuração de Foo.

Eu não acho que isso anula o propósito de especificar um valor padrão. O valor padrão é uma conveniência para chamar código, mas acho que você deve ser explícito em seus testes. Digamos que você possa deixar de especificar o boolparâmetro. O que acontece se, no futuro, alguém alterar o valor padrão de bpara true? Isto levará a testes de falha (e com razão), mas eles vão ser mais difíceis de correção por causa da suposição oculto que bé false. A especificação explícita do boolparâmetro tem outro benefício: melhora a legibilidade dos seus testes. Alguém que passar por eles saberá rapidamente que há uma Foofunção que aceita dois parâmetros. Esses são meus 2 centavos, pelo menos :)

Quanto a especificá-lo toda vez que você o zomba, não duplique o código: crie e / ou inicialize o mock em uma função, para que você tenha apenas um único ponto de mudança. Se você realmente deseja, pode superar a aparente falha do Moq aqui duplicando Fooos parâmetros nesta função de inicialização:

public void InitFooFuncOnFooMock(Mock<IFoo> fooMock, string a, bool b = false)
{
    if(!b)
    {
        fooMock.Setup(mock => mock.Foo(a, b)).Returns(false);
    }
    else
    {
        ...
    }
}
Chris Mantle
fonte
1
Excelente resposta; Eu já fui adiante e o especifiquei explicitamente em minhas zombarias, mas sua resposta confirma de uma maneira muito clara e lógica por que devo fazê-lo dessa maneira. Obrigado, @ Chris.
Appulus 20/10/12
9
alterar um parâmetro padrão "deve" interromper os testes. Não ter testes falhando quando um padrão é alterado pode ser um sinal de teste ruim. O código pode usar os padrões, mas os testes não?
Pop Catalin
Já faz um tempo, mas tentei essa abordagem com o Moq ao tentar zombar de uma interface (IDConnection no Dapper) e ainda estou recebendo o mesmo erro. Alguma idéia do porquê? Linha de amostra: mockDB.Setup (x => x.Query <MyObject> (It.IsAny <string> (), It.IsAny <DynamicParameters> (), It.IsAny <IDbTransaction> (), false, 600). Retorna (nova List <MyObject> ()); Os dois últimos valores são os parâmetros opcionais no método que estou configurando.
Raelshark
4
Arrgggh! O horrível if (!x) {} else {}anti-padrão :)
nicodemus13
1
@ nicodemus13 Sim, mas eu estava tentando manter o exemplo de código o mais próximo possível do exemplo do OP na pergunta. Eu não necessariamente defendem que :)
Chris Mantle
8

Acabei de encontrar esse problema hoje, o Moq não suporta esse caso de uso. Portanto, parece que substituir o método seria suficiente para este caso.

public interface IFoo
{
    bool Foo(string a);

    bool Foo(string a, bool b);
}

Agora os dois métodos estão disponíveis e este exemplo funcionaria:

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>())).Returns(false);
Gil Grossman
fonte
2

Usando o Moq versão 4.10.1, consegui fazer o seguinte

Com interface:

public interface IFoo
{
    bool Foo(string a, bool b = false);
}

E simulado

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>(), It.IsAny<bool>())).Returns(false);

Resolve uma chamada para Foo com o primeiro parâmetro ok

CF5
fonte