Como faço para usar o Moq para simular um método de extensão?


87

Estou escrevendo um teste que depende dos resultados de um método de extensão, mas não quero que uma falha futura desse método de extensão interrompa esse teste. Zombar desse resultado parecia a escolha óbvia, mas Moq não parece oferecer uma maneira de substituir um método estático (um requisito para um método de extensão). Existe uma ideia semelhante com Moq.Protected e Moq.Stub, mas eles não parecem oferecer nada para este cenário. Estou perdendo alguma coisa ou devo fazer isso de uma maneira diferente?

Aqui está um exemplo trivial que falha com a usual "Expectativa inválida em um membro não substituível" . Este é um mau exemplo de necessidade de simular um método de extensão, mas deve servir.

public class SomeType {
    int Id { get; set; }
}

var ListMock = new Mock<List<SomeType>>();
ListMock.Expect(l => l.FirstOrDefault(st => st.Id == 5))
        .Returns(new SomeType { Id = 5 });

Quanto a qualquer viciado em TypeMock que possa sugerir que eu use o Isolator em vez disso: agradeço o esforço, pois parece que o TypeMock poderia fazer o trabalho vendado e embriagado, mas nosso orçamento não aumentará tão cedo.


3
Uma cópia pode ser encontrada aqui: stackoverflow.com/questions/2295960/… .
Oliver

6
Esta pergunta é um ano mais velha do que aquela. Se houver duplicação, ocorre o contrário.
patridge

2
Ainda não há solução adequada em 2019!
TanvirArjel

1
@TanvirArjel Na verdade, há muitos anos você pode usar JustMock para simular métodos de extensão. E é tão simples quanto zombar de qualquer outro método. Aqui está um link para a documentação: Zombaria de métodos de extensão
Mihail Vladov

Respostas:


69

Os métodos de extensão são apenas métodos estáticos disfarçados. Mocking frameworks como Moq ou Rhinomocks só podem criar instâncias mock de objetos, isso significa que mocking métodos estáticos não é possível.


68
@ Mendelt..Então, como uma unidade testaria um método que possui internamente um método de extensão? quais são as alternativas possíveis?
Sai Avinash

@Mendelt, como uma unidade testaria um método que possui internamente um método de extensão? quais são as alternativas possíveis?
Alexander

@Alexander Eu tinha a mesma pergunta e então respondi de forma fantástica: agooddayforscience.blogspot.com/2017/08/… -Dê uma olhada nisso, é um salva-vidas!
LTV

30

Se você pode alterar o código dos métodos de extensão, você pode codificá-lo assim para poder testar:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

public static class MyExtensions
{
    public static IMyImplementation Implementation = new MyImplementation();

    public static string MyMethod(this object obj)
    {
        return Implementation.MyMethod(obj);
    }
}

public interface IMyImplementation
{
    string MyMethod(object obj);
}

public class MyImplementation : IMyImplementation
{
    public string MyMethod(object obj)
    {
        return "Hello World!";
    }
}

Portanto, os métodos de extensão são apenas um invólucro em torno da interface de implementação.

(Você poderia usar apenas a classe de implementação sem métodos de extensão que são uma espécie de açúcar sintático.)

E você pode simular a interface de implementação e defini-la como implementação para a classe de extensões.

public class MyClassUsingExtensions
{
    public string ReturnStringForObject(object obj)
    {
        return obj.MyMethod();
    }
}

[TestClass]
public class MyTests
{
    [TestMethod]
    public void MyTest()
    {
        // Given:
        //-------
        var mockMyImplementation = new Mock<IMyImplementation>();

        MyExtensions.Implementation = mockMyImplementation.Object;

        var myClassUsingExtensions = new MyClassUsingExtensions();

        // When:
        //-------
        var myObject = new Object();
        myClassUsingExtensions.ReturnStringForObject(myObject);

        //Then:
        //-------
        // This would fail because you cannot test for the extension method
        //mockMyImplementation.Verify(m => m.MyMethod());

        // This is success because you test for the mocked implementation interface
        mockMyImplementation.Verify(m => m.MyMethod(myObject));
    }
}

Muito obrigado por isso. Foi extremamente útil e me ajudou a superar alguns obstáculos nos testes.
DerHaifisch

Este é um comentário subestimado.
Raj


15

Eu criei uma classe wrapper para os métodos de extensão que eu precisava simular.

public static class MyExtensions
{
    public static string MyExtension<T>(this T obj)
    {
        return "Hello World!";
    }
}

public interface IExtensionMethodsWrapper
{
    string MyExtension<T>(T myObj);
}

public class ExtensionMethodsWrapper : IExtensionMethodsWrapper
{
    public string MyExtension<T>(T myObj)
    {
        return myObj.MyExtension();
    }
}

Em seguida, você pode simular os métodos de wrapper em seus testes e código com seu contêiner IOC.


Esta é uma solução alternativa em que você não pode mais usar a sintaxe dos métodos de extensão em seu código. Mas ajuda quando você não pode alterar a classe dos métodos de extensão.
informatorius

@informatorius O que você quer dizer com isso? Em seu código, você usa MyExtension () da classe MyExtensions. Em seus testes, você usa MyExtension () da classe ExtensionMethodsWrapper () que fornece o parâmetro.
ranthonissen

Se minha classe em teste usa MyExtension () de MyExtensions, então não posso simular as MyExtensions. Portanto, a classe em teste deve usar o IExtensionMethodsWrapper para que possa ser simulada. Mas a classe em teste não pode mais usar a sintaxe do método de extensão.
informatorius

1
Esse é o ponto principal do OP. Esta é uma solução alternativa para isso.
ranthonissen

4

Para métodos de extensão, normalmente uso a seguinte abordagem:

public static class MyExtensions
{
    public static Func<int,int, int> _doSumm = (x, y) => x + y;

    public static int Summ(this int x, int y)
    {
        return _doSumm(x, y);
    }
}

Ele permite injetar _doSumm com bastante facilidade.


2
Acabei de tentar isso, mas há problemas ao passar o objeto simulado Parent para o qual a extensão se destina ao passá-lo para algum outro método que está em outro assembly. Nesse caso, mesmo com essa técnica, a extensão original é selecionada quando o método é chamado em outra montagem, uma espécie de problema de escopo. Se eu chamar diretamente no projeto de Teste de Unidade, está tudo bem, mas quando você está testando outro código que chama o método de extensão, isso simplesmente não ajuda.
Stephen York

@Stephen Não tenho certeza de como evitar isso. Eu nunca tive esse tipo de problema de escopo
dmigo de

0

A melhor coisa que você pode fazer é fornecer uma implementação personalizada para o tipo que tem o método de extensão, por exemplo:

[Fact]
public class Tests
{
    public void ShouldRunOk()
    {
        var service = new MyService(new FakeWebHostEnvironment());

        // Service.DoStuff() internally calls the SomeExtensionFunction() on IWebHostEnvironment
        // Here it works just fine as we provide a custom implementation of that interface
        service.DoStuff().Should().NotBeNull();
    }
}

public class FakeWebHostEnvironment : IWebHostEnvironment
{
    /* IWebHostEnvironment implementation */

    public bool SomeExtensionFunction()
    {
        return false;
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.