Як використовувати Moq для знущання над методом розширення?


87

Я пишу тест, який залежить від результатів методу розширення, але я не хочу, щоб майбутній збій цього методу розширення коли-небудь порушив цей тест. Знущання над цим результатом здавалося очевидним вибором, але , здається, Moq не пропонує способу замінити статичний метод (вимога до методу розширення). Існує подібна ідея з Moq.Protected та Moq.Stub, але, схоже, вони нічого не пропонують для цього сценарію. Мені чогось не вистачає чи я повинен робити це по-іншому?

Ось тривіальний приклад, який зазнає невдачі із звичайним "Недійсним очікуванням на неперевірений член" . Це поганий приклад необхідності знущатися над методом розширення, але він повинен це зробити.

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

Що стосується будь-яких наркоманів TypeMock, які можуть запропонувати використовувати замість цього Isolator: я вдячний за зусилля, оскільки схоже, що TypeMock міг би виконувати цю роботу із зав'язаними очима та запою, але наш бюджет скоро не збільшується.


3
Дублікат можна знайти тут: stackoverflow.com/questions/2295960/… .
Олівер,

6
Це питання на цілий рік старше цього. Якщо є дублювання, воно йде іншим шляхом.
patridge

2
Досі немає відповідного рішення у 2019 році!
TanvirArjel

1
@TanvirArjel Насправді, з багатьох років ви могли використовувати JustMock для знущань над методами розширення. І такий же простий, як і знущання над будь-яким іншим способом. Ось посилання на документацію: Методи розширення Знущання
Михайло Владов

Відповіді:


69

Методи розширення - це просто замасковані статичні методи. Знущальні фреймворки, такі як Moq або Rhinomocks, можуть створювати лише фіктивні екземпляри об’єктів, це означає, що знущання над статичними методами неможливо.


68
@ Мендельт .. Тоді як би одна одиниця перевірила метод, який внутрішньо має метод розширення? які можливі альтернативи?
Сай Авінаш

@Mendelt, як би одна одиниця перевірила метод, який внутрішньо має метод розширення? які можливі альтернативи?
Олександр

@Alexander У мене було те саме запитання, а потім це відповіло на нього фантастично: agooddayforscience.blogspot.com/2017/08/… -Погляньте на це, це порятунок життя!
LTV

30

Якщо ви можете змінити код методів розширення, тоді ви можете кодувати його таким чином, щоб мати можливість тестувати:

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

Тож методи розширення - це лише обгортка навколо інтерфейсу реалізації.

(Ви можете використовувати лише клас реалізації без методів розширення, які є свого роду синтаксичним цукром.)

І ви можете знущатися над інтерфейсом реалізації та встановити його як реалізацію для класу розширень.

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

Щиро дякую за це. Це було надзвичайно корисно і змусило мене перешкодити тестуванню.
DerHaifisch

Це недооцінений коментар.
Радж


15

Я створив клас обгортки для методів розширення, над якими мені потрібно було знущатися.

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

Тоді ви можете знущатись із методами обгортки у тестах та кодом за допомогою контейнера IOC.


Це обхідне рішення, коли ви більше не можете використовувати синтаксис методів розширення у своєму коді. Але це допомагає, коли ви не можете змінити клас методів розширення.
informatorius

@informatorius Що ви маєте на увазі під цим? У коді ви використовуєте MyExtension () із класу MyExtensions. У своїх тестах ви використовуєте MyExtension () із класу ExtensionMethodsWrapper (), що забезпечує параметр.
ranthonissen

Якщо мій тестований клас використовує MyExtension () з MyExtensions, тоді я не можу знущатися над MyExtensions. Отже, тестований клас повинен використовувати IExtensionMethodsWrapper, щоб над ним можна було знущатися. Але тоді тестований клас більше не може використовувати синтаксис методу розширення.
informatorius

1
У цьому вся суть ОП. Це обхідний шлях для цього.
ranthonissen

4

Для методів розширення я зазвичай використовую такий підхід:

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

Це дозволяє досить просто вводити _doSumm.


2
Я щойно спробував це, але виникають проблеми при передачі знущаного об’єкта батьків, для якого призначене розширення при передачі його в інший метод, який знаходиться в іншій збірці. У цьому випадку навіть за допомогою цієї техніки оригінальне розширення вибирається, коли метод викликається в іншій збірці, щось на зразок питання обсягу. Якщо я зателефоную безпосередньо в проект Unit Test, це нормально, але коли ви тестуєте інший код, який викликає метод розширення, це просто не допомагає.
Стівен Йорк,

@Stephen Не знаю, як цього уникнути. У мене ніколи не було такого випуску із
сферою дії

0

Найкраще, що ви можете зробити, це надати власну реалізацію для типу, який має метод розширення, наприклад:

[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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.