Как использовать 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
Этот вопрос на год старше этого. Если есть дублирование, все идет по другому пути.
Патридж
2
По-прежнему нет подходящего решения в 2019 году!
TanvirArjel
1
@TanvirArjel На самом деле, уже много лет вы можете использовать JustMock для имитации методов расширения. И это так же просто, как издевательство над любым другим методом. Вот ссылка на документацию: Extension Methods Mocking
Михаил Владов

Ответы:

69

Методы расширения - это всего лишь замаскированные статические методы. Фреймворки имитации, такие как Moq или Rhinomocks, могут создавать только имитирующие экземпляры объектов, это означает, что имитация статических методов невозможна.

Мендельт
источник
68
@ Mendelt .. Тогда как бы можно было провести модульное тестирование метода, у которого есть внутренний метод расширения? каковы возможные альтернативы?
Сай Авинаш
@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.

Rnthonissen
источник
Это обходной путь, при котором вы больше не можете использовать синтаксис методов расширения в своем коде. Но это помогает, когда вы не можете изменить класс методов расширения.
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.

dmigo
источник
2
Я просто попробовал это, но возникают проблемы при передаче имитационного объекта Parent, для которого предназначено расширение, при передаче его в какой-либо другой метод, который находится в другой сборке. В этом случае даже при использовании этого метода исходное расширение выбирается при вызове метода в другой сборке, что является своего рода проблемой области видимости. Если я вызываю непосредственно в проекте модульного теста, это нормально, но когда вы тестируете другой код, который вызывает метод расширения, это просто не помогает.
Стивен Йорк
@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;
    }
}
Росс
источник