Методы расширения с помощью Moq

У меня есть существующий интерфейс...

public interface ISomeInterface
{
    void SomeMethod();
}

и я расширил этот интерфейс, используя миксин...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}

У меня есть класс, называющий это, который я хочу проверить...

public class Caller
{
    private readonly ISomeInterface someInterface;

    public Caller(ISomeInterface someInterface)
    {
        this.someInterface = someInterface;
    }

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}

и тест, в котором я хотел бы смоделировать интерфейс и проверить вызов метода расширения...

    [Test]
    public void Main_BasicCall_CallsAnotherMethod()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();
        someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable();

        var caller = new Caller(someInterfaceMock.Object);

        // Act
        caller.Main();

        // Assert
        someInterfaceMock.Verify();
    }

Запуск этого теста, однако, генерирует исключение...

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()

Мой вопрос, есть ли хороший способ сделать миксин-колл?

10 ответов

Я использовал Обертку, чтобы обойти эту проблему. Создайте объект-обёртку и передайте ваш смоделированный метод.

См. Статические методы Mocking Static для модульного тестирования Пола Ирвина, здесь есть хорошие примеры.

Вы не можете "напрямую" макетировать статический метод (следовательно, метод расширения) с помощью фреймворка. Вы можете попробовать Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx), бесплатный инструмент от Microsoft, который реализует другой подход. Вот описание инструмента:

Moles - это легковесная структура для тестовых заглушек и обходов в.NET, основанная на делегатах.

Кроты могут использоваться для обхода любого метода.NET, включая не виртуальные / статические методы в закрытых типах.

Вы можете использовать Кроты с любой средой тестирования (это не зависит от этого).

Я обнаружил, что мне нужно обнаружить внутреннюю часть метода расширения, для которого я пытался смоделировать ввод, и смоделировать то, что происходило внутри расширения.

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

Вы можете смоделировать тестовый интерфейс, который наследуется от реального и имеет элемент с такой же сигнатурой, что и у метода расширения.

Затем вы можете смоделировать тестовый интерфейс, добавить реальный в макет и вызвать метод теста в настройке.

Ваша реализация макета может затем вызвать любой метод, который вы хотите, или просто проверить, что метод вызывается:

IReal //on which some extension method is defined
{
    ... SomeNotAnExtensionMethod(...);
}

ITest: IReal
{
    ... SomeExtensionMethod(...);
}

var someMock = new Mock<ITest>();
Mock.As<IReal>(); //ad IReal to the mock
someMock.Setup(x => x.SomeExtensionMethod()).Verifiable(); //Calls SomeExtensionMethod on ITest
someMock.As<IReal>().Setup(x => x.SomeNotAnExtensionMethod()).Verifiable(); //Calls SomeNotAnExtensionMethod on IReal

Спасибо решению Håvard S в этом посте за то, как реализовать макет, поддерживающий интерфейс. Как только я нашел его, адаптировать его к тестовому интерфейсу и статическому методу было очень просто.

Вы можете легко смоделировать метод расширения с помощью JustMock. API такой же, как и обычный метод имитации. Рассмотрим следующие

public static string Echo(this Foo foo, string strValue) 
{ 
    return strValue; 
}

Чтобы организовать и проверить этот метод, используйте следующее:

string expected = "World";

var foo = new Foo();
Mock.Arrange(() => foo.Echo(Arg.IsAny<string>())).Returns(expected);

string result = foo.Echo("Hello");

Assert.AreEqual(expected, result);

Вот также ссылка на документацию: Extension Methods Mocking

Заявление об ограничении ответственности. Я один из разработчиков JustMock.

Если вы просто хотите убедиться, что метод расширения был вызван, и вы не пытаетесь настроить возвращаемое значение, вы можете проверить Invocations свойство на имитируемом объекте.

Как это:

      var invocationsCount = mockedObject.Invocations.Count;
invocationsCount.Should().BeGreaterThan(0);

Мне нравится использовать обертку (шаблон адаптера), когда я обертываю сам объект. Я не уверен, что использовал бы это для упаковки метода расширения, который не является частью объекта.

Я использую внутреннее свойство Lazy Injectable либо типа Action, Func, Predicate, либо делегата, и разрешаю вводить (заменять) метод во время модульного теста.

    internal Func<IMyObject, string, object> DoWorkMethod
    {
        [ExcludeFromCodeCoverage]
        get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
        set { _DoWorkMethod = value; }
    } private Func<IMyObject, string, object> _DoWorkMethod;

Затем вы вызываете Func вместо фактического метода.

    public object SomeFunction()
    {
        var val = "doesn't matter for this example";
        return DoWorkMethod.Invoke(MyObjectProperty, val);
    }

Для более полного примера, проверьте http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

Причина, по которой невозможно издеваться над методом расширения, уже дана в хороших ответах. Я просто пытаюсь предложить другое возможное решение с этим ответом: извлеките защищенный виртуальный метод с вызовом метода расширения и создайте настройку для этого метода в тестовом классе / методе с помощью прокси.

      public class Foo
{
  public void Method()
    => CallToStaticMethod();

  protected virtual void CallToStaticMethod()
    => StaticClass.StaticMethod();
}

и тест

      [TestMethod]
    public void MyTestMethod()
    {
        var expected = new Exception("container exception");
        var proxy = new Mock<Foo>();
        proxy.Protected().Setup("CallToStaticMethod").Throws(expected);

        var actual = Assert.ThrowsException<Exception>(() => proxy.Object.Foo());

        Assert.AreEqual(expected, actual);
    }
  

В моем случае метод расширения — это метод вокруг некоторого общедоступного метода моего класса. Поэтому я проверил вызов этого внутреннего метода. Этот подход похож на ответ Алвиса (выше).

Итак, если вы используете Moq и хотите имитировать результат метода расширения, вы можете использовать SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn()) в экземпляре фиктивного класса, у которого есть метод расширения, который вы пытаетесь имитировать.

Он не идеален, но для целей модульного тестирования работает хорошо.

Другие вопросы по тегам