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.

patridge
fonte
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.

Mendelt
fonte
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));
    }
}
informatorius
fonte
Muito obrigado por isso. Foi extremamente útil e me ajudou a superar alguns obstáculos nos testes.
DerHaifisch
Este é um comentário subestimado.
Raj
16

Eu sei que esta pergunta não está ativa há cerca de um ano, mas a Microsoft lançou uma estrutura para lidar exatamente com isso, chamado Moles .

Aqui estão alguns tutoriais também:

  • DimeCasts.net
  • Tutorial de Nikolai Tillman

  • Mike Fielden
    fonte
    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.

    Ranthonissen
    fonte
    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.

    dmigo
    fonte
    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;
        }
    }
    
    Ross
    fonte