Zombando de métodos de extensão com Moq

175

Eu tenho uma interface preexistente ...

public interface ISomeInterface
{
    void SomeMethod();
}

e eu estendi essa interface usando um mixin ...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}

Eu tenho uma classe que está chamando isso que eu quero testar ...

public class Caller
{
    private readonly ISomeInterface someInterface;

    public Caller(ISomeInterface someInterface)
    {
        this.someInterface = someInterface;
    }

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}

e um teste em que eu gostaria de zombar da interface e verificar a chamada para o método de extensão ...

    [Test]
    public void Main_BasicCall_CallsAnotherMethod()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();
        someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable();

        var caller = new Caller(someInterfaceMock.Object);

        // Act
        caller.Main();

        // Assert
        someInterfaceMock.Verify();
    }

A execução deste teste, no entanto, gera uma exceção ...

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()

Minha pergunta é: existe uma boa maneira de zombar da chamada de mixin?

Russell Giddings
fonte
3
Na minha experiência, os termos métodos de mixin e extensão são coisas separadas. Eu usaria o último neste caso para evitar confusões: P
Ruben Bartelink 19/02/10
3
Duplicado de: stackoverflow.com/questions/562129/… .
Oliver

Respostas:

33

Você não pode "diretamente" simular o método estático (portanto, o método de extensão) com a estrutura de simulação. Você pode experimentar o Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ), uma ferramenta gratuita da Microsoft que implementa uma abordagem diferente. Aqui está a descrição da ferramenta:

Moles é uma estrutura leve para stubs de teste e desvios no .NET que se baseia em delegados.

As toupeiras podem ser usadas para desviar qualquer método .NET, incluindo métodos não virtuais / estáticos em tipos fechados.

Você pode usar Moles com qualquer estrutura de teste (é independente disso).

Daniele Armanasco
fonte
2
Além dos Moles, existem outras estruturas de simulação (não gratuitas) que usam a API de criação de perfil do .NET para simular objetos e, portanto, podem substituir qualquer chamada. Os dois que conheço são o JustMock e o Isolador TypeMock da Telerik .
Marcel Gosselin
6
Teoricamente, as toupeiras são boas, mas eu encontrei três problemas ao testá-la que me impediram de usá-la ... 1) Ela não é executada no corredor do Resharper NUnit 2) É necessário criar manualmente um conjunto de toupeiras para cada conjunto cortado. ) Você precisa recriar manualmente um conjunto de toupeiras sempre que um método de stub é alterado.
Russell Giddings
26

Eu usei um Wrapper para solucionar esse problema. Crie um objeto wrapper e passe seu método simulado.

Veja Mocking Static Methods for Unit Testing de Paul Irwin, ele tem bons exemplos.

Alvis
fonte
12
Eu gosto desta resposta porque o que está dizendo (sem dizer diretamente) é que você precisa alterar seu código para torná-lo testável. É assim que funciona. Entenda que no design de microchip / IC / ASIC, esses chips precisam não apenas ser projetados para funcionar, mas ainda mais para serem testáveis, porque se você não pode testar um microchip, ele é inútil - você não pode garantir que trabalhos. O mesmo vale para o software. Se você não o construiu para ser testável, é ... inútil. Crie para ser testável, o que, em alguns casos, significa reescrever o código (e usar wrappers) e, em seguida, criar os testes automatizados que o testam.
Michael Plautz
2
Criei uma pequena biblioteca que envolve Dapper, Dapper.Contrib e IDbConnection. github.com/codeapologist/DataAbstractions.Dapper
Drew Sumido
15

Eu descobri que tinha que descobrir o interior do método de extensão para o qual estava tentando zombar da entrada e zombar do que estava acontecendo dentro da extensão.

Eu vi usando uma extensão como adicionar código diretamente ao seu método. Isso significava que eu precisava zombar do que acontece dentro da extensão e não da própria extensão.

chris31389
fonte
11

Você pode simular uma interface de teste que herda da real e tem um membro com a mesma assinatura que o método de extensão.

Você pode simular a interface de teste, adicionar a real à simulação e chamar o método de teste na configuração.

Sua implementação do mock pode chamar o método desejado ou simplesmente verificar se o método é chamado:

IReal //on which some extension method is defined
{
    ... SomeRegularMethod(...);
}

static ExtensionsForIReal
{
    static ... SomeExtensionMethod(this IReal iReal,...);
}

ITest: IReal
{
    //This is a regular method with same name and signature as the extension without the "this IReal iReal" parameter
    ... SomeExtensionMethod(...);
}

var someMock = new Mock<ITest>();
Mock.As<IReal>(); //ad IReal to the mock
someMock.Setup(x => x.SomeExtensionMethod(...)).Verifiable(); //Calls SomeExtensionMethod on ITest
someMock.As<IReal>().Setup(x => x.SomeRegularMethod(...)).Verifiable(); //Calls SomeRegularMethod on IReal

Agradecemos à solução Håvard S neste post por saber como implementar uma simulação que suporta duas interfaces. Depois que o encontrei, adaptá-lo com a interface de teste e o método estático foi uma caminhada.

pasx
fonte
Você pode nos mostrar a mesma amostra de assinatura para SomeNotAnExtensionMethode SomeNotAnExtensionMethod? Agora eu tenho idéia de como criar uma assinatura de método de extensão dentro de uma interface ...
Peter Csala 03/07
1
@ Peter Csala: Desculpe se isso não está claro. Atualizei a postagem para torná-la mais clara e renomeei SomeNotAnExtensionMethod para SomeRegularMethod.
pasx 03/07
Como você passa o iRealparâmetro para SomeExtensionMethodno caso de ITest? Se você o passar como primeiro parâmetro, como você o instalará? Setup( x=> x.SomeExtensionMethod(x, ...)isso causará uma exceção de tempo de execução.
Peter Csala 03/07
Você não passa. Do ponto de vista da simulação, o ITest contém um método regular, sem o parâmetro this, para corresponder a assinatura de chamadas ao método de extensão no código, para que você nunca receba um IReal aqui e, em qualquer caso, a implementação do IReal / ITest é a própria simulação. . Se você deseja acessar algumas propriedades do IReal simulado em SomeExtensionMethod, faça tudo isso no exemplo, por exemplo: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (...) .. Callback (() => você pode acesso _mockCache aqui);)
pasx 03/07
8

Você pode facilmente zombar de um método de extensão com o JustMock . A API é o mesmo que zombar do método normal. Considere o seguinte

public static string Echo(this Foo foo, string strValue) 
{ 
    return strValue; 
}

Para organizar e verificar esse método, use o seguinte:

string expected = "World";

var foo = new Foo();
Mock.Arrange(() => foo.Echo(Arg.IsAny<string>())).Returns(expected);

string result = foo.Echo("Hello");

Assert.AreEqual(expected, result);

Aqui também está um link para a documentação: Métodos de extensão Mocking

Mihail Vladov
fonte
2

Eu gosto de usar o wrapper (padrão do adaptador) quando estou quebrando o próprio objeto. Não tenho certeza se usaria isso para agrupar um método de extensão, que não faz parte do objeto.

Eu uso uma Propriedade Injectável Preguiçosa interna do tipo Action, Func, Predicate ou delegate e permito injetar (trocar) o método durante um teste de unidade.

    internal Func<IMyObject, string, object> DoWorkMethod
    {
        [ExcludeFromCodeCoverage]
        get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
        set { _DoWorkMethod = value; }
    } private Func<IMyObject, string, object> _DoWorkMethod;

Então você chama o Func em vez do método real.

    public object SomeFunction()
    {
        var val = "doesn't matter for this example";
        return DoWorkMethod.Invoke(MyObjectProperty, val);
    }

Para um exemplo mais completo, consulte http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

Rhyous
fonte
Isso é bom, mas os leitores devem estar cientes de que _DoWorkMethod é um novo campo da classe que cada instância da classe agora precisa alocar mais um campo. É raro que isso importe, mas às vezes depende do número de instâncias que você está alocando a qualquer momento. Você pode solucionar esse problema, tornando _DoWorkMethod estático. A desvantagem disso é que, se você tiver testes de unidade em execução simultaneamente, serão concluídos dois testes de unidade diferentes que podem modificar o mesmo valor estático.
Zumalifeguard 2/11
-1

Portanto, se você estiver usando o Moq e quiser zombar do resultado de um método Extension, poderá usar SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())na instância da classe mock que possui o método de extensão que você está tentando zombar.

Não é perfeito, mas, para fins de teste de unidade, funciona bem.

ckernel
fonte
Eu acredito que é SetReturnsDefault <T> ()
David
isso nunca retorna a instância concreta. Apenas nulo no caso de uma classe c # personalizada!
HelloWorld