Jak używać Moq do mockowania metody rozszerzenia?

87

Piszę test, który zależy od wyników metody rozszerzającej, ale nie chcę, aby przyszła awaria tej metody rozszerzającej kiedykolwiek przerwała ten test. Kpienie z tego wyniku wydawało się oczywistym wyborem, ale Moq nie wydaje się oferować sposobu na zastąpienie metody statycznej (wymóg dla metody rozszerzającej). Podobny pomysł jest z Moq.Protected i Moq.Stub, ale wydaje się, że nie oferują one niczego w tym scenariuszu. Czy czegoś mi brakuje, czy powinienem zająć się tym inaczej?

Oto trywialny przykład, który kończy się niepowodzeniem ze zwykłym „Nieprawidłowym oczekiwaniem na nieprzekraczalny element członkowski” . To zły przykład potrzeby kpienia z metody rozszerzającej, ale powinno.

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 });

Jeśli chodzi o każdego ćpuna TypeMock, który mógłby zasugerować, żebym zamiast tego użył Izolatora: doceniam wysiłek, ponieważ wygląda na to, że TypeMock mógłby wykonać pracę z zawiązanymi oczami i nietrzeźwym, ale nasz budżet nie zwiększa się w najbliższym czasie.

patridge
źródło
3
Duplikat można znaleźć tutaj: stackoverflow.com/questions/2295960/… .
Oliver
6
To pytanie jest o cały rok starsze od tamtego. Jeśli występuje duplikacja, idzie w drugą stronę.
patridge
2
Wciąż brak odpowiedniego rozwiązania w 2019 roku!
TanvirArjel
1
@TanvirArjel Właściwie od wielu lat można było używać JustMocka do mockowania metod rozszerzających. I jest tak proste, jak kpienie z każdej innej metody. Oto link do dokumentacji: Metody rozszerzeń Mocking
Mihail Vladov,

Odpowiedzi:

69

Metody rozszerzające to po prostu metody statyczne w przebraniu. Mockowanie frameworków, takich jak Moq czy Rhinomocks, może tworzyć tylko pozorowane instancje obiektów, co oznacza, że ​​mockowanie metod statycznych nie jest możliwe.

Mendelt
źródło
68
@ Mendelt..Jak więc jedna jednostka przetestuje metodę, która wewnętrznie ma metodę rozszerzającą? jakie są możliwe alternatywy?
Sai Avinash
@Mendelt, w jaki sposób jedna jednostka przetestuje metodę, która wewnętrznie ma metodę rozszerzenia? jakie są możliwe alternatywy?
Alexander,
@Alexander Miałem to samo pytanie, a to fantastycznie odpowiedziało na nie: agooddayforscience.blogspot.com/2017/08/ ... -Spójrz na to, to ratuje życie!
LTV
30

Jeśli możesz zmienić kod metod rozszerzenia, możesz go zakodować w ten sposób, aby móc testować:

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!";
    }
}

Zatem metody rozszerzające są jedynie otoką wokół interfejsu implementacji.

(Możesz użyć tylko klasy implementacji bez metod rozszerzających, które są rodzajem cukru składniowego).

Możesz też mockować interfejs implementacji i ustawić go jako implementację dla klasy rozszerzeń.

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
źródło
Bardzo ci za to dziękuję. To było niezwykle przydatne i pomogło mi pokonać pewne przeszkody w testowaniu.
DerHaifisch
To niedoceniany komentarz.
Raj
15

Utworzyłem klasę opakowującą dla metod rozszerzających, których potrzebowałem do mockowania.

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();
    }
}

Następnie możesz mockować metody opakowania w testach i kodować za pomocą kontenera IOC.

ranthonissen
źródło
Jest to obejście problemu, w którym nie można już używać składni metod rozszerzających w kodzie. Ale pomaga, gdy nie można zmienić klasy metod rozszerzających.
informatorius
@informatorius Co przez to rozumiesz? W swoim kodzie używasz MyExtension () z klasy MyExtensions. W swoich testach używasz MyExtension () z klasy ExtensionMethodsWrapper () dostarczającej parametr.
ranthonissen
Jeśli moja testowana klasa używa MyExtension () z MyExtensions, nie mogę kpić z MyExtensions. Dlatego testowana klasa musi używać IExtensionMethodsWrapper, aby można było z niej mockować. Jednak testowana klasa nie może już używać składni metody rozszerzającej.
informatorius
1
O to chodzi w PO. To jest obejście tego problemu.
ranthonissen
4

W przypadku metod rozszerzających zwykle używam następującego podejścia:

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);
    }
}

Pozwala dość łatwo wstrzyknąć _doSumm.

dmigo
źródło
2
Właśnie to wypróbowałem, ale występują problemy podczas przekazywania obiektu nadrzędnego mockowanego, do którego służy rozszerzenie, podczas przekazywania go do innej metody, która znajduje się w innym zestawie. W takim przypadku nawet w przypadku tej techniki oryginalne rozszerzenie jest wybierane, gdy metoda jest wywoływana w innym zestawie, co jest rodzajem problemu z zakresem. Jeśli zadzwonię bezpośrednio w projekcie testu jednostkowego, wszystko jest w porządku, ale gdy testujesz inny kod, który wywołuje metodę rozszerzenia, po prostu nie pomaga.
Stephen York,
@Stephen Nie wiem, jak tego uniknąć. Nigdy nie miałem tego rodzaju problemu z
zakresem
0

Najlepszą rzeczą, jaką możesz zrobić, jest zapewnienie niestandardowej implementacji dla typu, który ma metodę rozszerzenia, np .:

[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
źródło