Методы расширения с помощью 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())
в экземпляре фиктивного класса, у которого есть метод расширения, который вы пытаетесь имитировать.
Он не идеален, но для целей модульного тестирования работает хорошо.