Как использовать Moq для имитации метода расширения?

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


person patridge    schedule 18.02.2009    source источник
comment
Дубликат можно найти здесь: stackoverflow.com/questions/ 2295960 /.   -  person Oliver    schedule 12.10.2012
comment
Этот вопрос на год старше этого. Если есть дублирование, все идет по другому пути.   -  person patridge    schedule 12.10.2012
comment
По-прежнему нет подходящего решения в 2019 году!   -  person TanvirArjel    schedule 19.01.2019
comment
@TanvirArjel На самом деле, многие годы вы могли использовать JustMock для имитации методов расширения. И это так же просто, как издеваться над любым другим методом. Вот ссылка на документацию: Мокинг методов расширения < / а>   -  person Mihail Vladov    schedule 07.02.2020


Ответы (6)


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

person Mendelt    schedule 18.02.2009
comment
@ Mendelt .. Тогда как бы можно было провести модульное тестирование метода, у которого есть внутренний метод расширения? каковы возможные альтернативы? - person Sai Avinash; 21.06.2016
comment
@Alexander У меня был тот же вопрос, и я ответил на него фантастическим образом: agooddayforscience. blogspot.com/2017/08/ - Взгляните на это, это спасатель жизни! - person letie; 29.08.2020

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

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));
    }
}
person informatorius    schedule 28.03.2014

Я знаю, что этот вопрос не был активен около года, но Microsoft выпустила платформу для обработки именно этого, которая называется Родинки.

Here are a few tutorials as well:

  • DimeCasts.net
  • Nikolai Tillman's Tutorial

    person Mike Fielden    schedule 28.04.2010

  • Я создал класс-оболочку для методов расширения, которые мне нужно было смоделировать.

    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.

    person ranthonissen    schedule 01.10.2010
    comment
    Это обходной путь, при котором вы больше не можете использовать синтаксис методов расширения в своем коде. Но это помогает, когда вы не можете изменить класс методов расширения. - person informatorius; 31.03.2019
    comment
    @informatorius Что вы имеете в виду? В вашем коде вы используете MyExtension () из класса MyExtensions. В своих тестах вы используете MyExtension () из класса ExtensionMethodsWrapper (), предоставляющего параметр. - person ranthonissen; 04.04.2019
    comment
    Если мой тестируемый класс использует MyExtension () из MyExtensions, я не могу издеваться над MyExtensions. Таким образом, тестируемый класс должен использовать IExtensionMethodsWrapper, чтобы его можно было высмеять. Но тогда тестируемый класс больше не может использовать синтаксис метода расширения. - person informatorius; 13.04.2019
    comment
    В этом весь смысл ОП. Это обходной путь. - person ranthonissen; 15.04.2019

    Для методов расширения я обычно использую следующий подход:

    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.

    person dmigo    schedule 29.01.2015
    comment
    Я просто попробовал это, но возникают проблемы при передаче имитационного объекта Parent, для которого предназначено расширение, при передаче его в какой-либо другой метод, который находится в другой сборке. В этом случае даже при использовании этого метода исходное расширение выбирается при вызове метода в другой сборке, что является своего рода проблемой области видимости. Если я вызываю непосредственно в проекте модульного теста, это нормально, но когда вы тестируете другой код, который вызывает метод расширения, это просто не помогает. - person Stephen York; 10.06.2016
    comment
    @Stephen Не знаю, как этого избежать. У меня никогда не было такой проблемы с определением объема - person dmigo; 10.06.2016

    Лучшее, что вы можете сделать, - это предоставить настраиваемую реализацию для типа, у которого есть метод расширения, например:

    [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;
        }
    }
    
    person Ross    schedule 17.06.2020