Wie verspotte ich mit Moq eine Erweiterungsmethode?

85

Ich schreibe einen Test, der von den Ergebnissen einer Erweiterungsmethode abhängt, aber ich möchte nicht, dass ein zukünftiger Fehler dieser Erweiterungsmethode diesen Test jemals bricht. Das Verspotten dieses Ergebnisses schien die naheliegende Wahl zu sein, aber Moq scheint keine Möglichkeit zu bieten, eine statische Methode zu überschreiben (eine Voraussetzung für eine Erweiterungsmethode). Es gibt eine ähnliche Idee mit Moq.Protected und Moq.Stub, aber sie scheinen für dieses Szenario nichts zu bieten. Vermisse ich etwas oder sollte ich das anders machen?

Hier ist ein triviales Beispiel, das mit der üblichen "Ungültige Erwartung an ein nicht überschreibbares Mitglied" fehlschlägt . Dies ist ein schlechtes Beispiel dafür, wie man eine Erweiterungsmethode verspotten muss, aber es sollte reichen.

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

Wie bei allen TypeMock-Junkies, die vorschlagen könnten, stattdessen Isolator zu verwenden: Ich schätze den Aufwand, da TypeMock anscheinend die Arbeit mit verbundenen Augen und betrunken erledigen kann, aber unser Budget steigt nicht so schnell.

Patridge
quelle
3
Ein Duplikat finden Sie hier: stackoverflow.com/questions/2295960/… .
Oliver
6
Diese Frage ist ein ganzes Jahr älter als diese. Wenn es Duplikate gibt, geht es in die andere Richtung.
Patridge
2
Noch keine passende Lösung im Jahr 2019!
TanvirArjel
1
Eigentlich @TanvirArjel, von vielen Jahren könnten Sie JustMock zu Mock - Erweiterungsmethoden. Und ist so einfach wie jede andere Methode zu verspotten. Hier ist ein Link zur Dokumentation: Extension Methods Mocking
Mihail Vladov

Antworten:

67

Erweiterungsmethoden sind nur verschleierte statische Methoden. Das Verspotten von Frameworks wie Moq oder Rhinomocks kann nur Scheininstanzen von Objekten erstellen. Dies bedeutet, dass das Verspotten statischer Methoden nicht möglich ist.

Mendelt
quelle
65
@ Mendelt..Wie würde dann eine Einheit eine Methode testen, die intern eine Erweiterungsmethode hat? Was sind die möglichen Alternativen?
Sai Avinash
@ Mendelt, wie würde eine Einheit eine Methode testen, die intern eine Erweiterungsmethode hat? Was sind die möglichen Alternativen?
Alexander
@Alexander Ich hatte die gleiche Frage und diese hat sie fantastisch beantwortet: agooddayforscience.blogspot.com/2017/08/… - Schauen Sie sich das an, es ist ein Lebensretter!
LTV
30

Wenn Sie den Code für Erweiterungsmethoden ändern können, können Sie ihn wie folgt codieren, um Folgendes testen zu können:

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

Die Erweiterungsmethoden sind also nur ein Wrapper um die Implementierungsschnittstelle.

(Sie können nur die Implementierungsklasse ohne Erweiterungsmethoden verwenden, die eine Art syntaktischen Zucker darstellen.)

Sie können die Implementierungsschnittstelle verspotten und als Implementierung für die Erweiterungsklasse festlegen.

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
quelle
Vielen Dank dafür. Es war äußerst nützlich und hat mich beim Testen über einige Hürden gebracht.
DerHaifisch
Dies ist ein unterschätzter Kommentar.
Raj
16

Ich weiß, dass diese Frage seit ungefähr einem Jahr nicht mehr aktiv ist, aber Microsoft hat ein Framework veröffentlicht, das genau dies behandelt und Moles heißt .

Hier sind auch einige Tutorials:

  • DimeCasts.net
  • Nikolai Tillmans Tutorial

  • Mike Fielden
    quelle
    13

    Ich habe eine Wrapper-Klasse für die Erweiterungsmethoden erstellt, die ich verspotten musste.

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

    Dann können Sie die Wrapper-Methoden in Ihren Tests verspotten und mit Ihrem IOC-Container codieren.

    Ranthonissen
    quelle
    Dies ist eine Problemumgehung, bei der Sie die Syntax der Erweiterungsmethoden in Ihrem Code nicht mehr verwenden können. Es ist jedoch hilfreich, wenn Sie die Klasse der Erweiterungsmethoden nicht ändern können.
    Informatorius
    @informatorius Was meinst du damit? In Ihrem Code verwenden Sie MyExtension () aus der MyExtensions-Klasse. In Ihren Tests verwenden Sie MyExtension () aus der ExtensionMethodsWrapper () -Klasse, die den Parameter bereitstellt.
    Ranthonissen
    Wenn meine getestete Klasse MyExtension () aus den MyExtensions verwendet, kann ich die MyExtensions nicht verspotten. Die zu testende Klasse muss also den IExtensionMethodsWrapper verwenden, damit sie verspottet werden kann. Dann kann die getestete Klasse die Syntax der Erweiterungsmethode nicht mehr verwenden.
    Informatorius
    1
    Das ist der springende Punkt des OP. Dies ist eine Problemumgehung dafür.
    Ranthonissen
    4

    Für Erweiterungsmethoden verwende ich normalerweise den folgenden Ansatz:

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

    Es erlaubt, _doSumm ziemlich einfach zu injizieren.

    dmigo
    quelle
    2
    Ich habe es gerade versucht, aber es gibt Probleme beim Übergeben des übergeordneten verspotteten Objekts, für das die Erweiterung bestimmt ist, wenn es an eine andere Methode übergeben wird, die sich in einer anderen Assembly befindet. In diesem Fall wird auch bei dieser Technik die ursprüngliche Erweiterung ausgewählt, wenn die Methode in der anderen Assembly aufgerufen wird, was eine Art Scoping-Problem darstellt. Wenn ich direkt im Unit Test-Projekt aufrufe, ist das zwar in Ordnung, aber wenn Sie anderen Code testen, der die Erweiterungsmethode aufruft, hilft das einfach nicht.
    Stephen York
    @ Stephen Ich bin mir nicht sicher, wie ich das vermeiden soll. Ich hatte noch nie so ein Scoping-Problem
    dmigo
    0

    Das Beste, was Sie tun können, ist, eine benutzerdefinierte Implementierung für den Typ bereitzustellen, der über die Erweiterungsmethode verfügt, z.

    [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
    quelle