Издевательские методы расширения с помощью Moq
У меня есть существующий интерфейс...
public interface ISomeInterface
{
void SomeMethod();
}
и я расширил это intreface, используя mixin...
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()
Мой вопрос: есть ли хороший способ издеваться над вызовом mixin?
Ответы
Ответ 1
Вы не можете "прямо" высмеять статический метод (следовательно, метод расширения) с насмешкой. Вы можете попробовать Moles (http://research.microsoft.com/en-us/projects/pex/downloads.aspx), бесплатный инструмент от Microsoft, который реализует другой подход.
Ниже приведено описание инструмента:
Moles - это легкая структура для тестовых заглушек и обходов в .NET, основанная на делегатах.
Моли могут использоваться для обхода любого метода .NET, включая не виртуальные/статические методы в закрытых типах.
Вы можете использовать Moles с любой структурой тестирования (независимо от этого).
Ответ 2
Я использовал Wrapper, чтобы обойти эту проблему. Создайте объект-обертку и передайте свой издеваемый метод.
См. Издевательские статические методы для модульного тестирования Paul Irwin, у него есть приятные примеры.
Ответ 3
Я обнаружил, что мне нужно обнаружить внутреннюю часть метода расширения, для которого я пытался смоделировать ввод, и смоделировать, что происходит внутри расширения.
Я рассматривал использование расширения как добавление кода непосредственно в ваш метод. Это означало, что мне нужно было высмеивать то, что происходит внутри расширения, а не само расширение.
Ответ 4
Мне нравится использовать оболочку (шаблон адаптера), когда я обматываю сам объект. Я не уверен, что использую это для переноса метода расширения, который не является частью объекта.
Я использую внутреннее Lazy Injectable Property любого типа Action, Func, Predicate или делегата и разрешаю впрыскивать (заменяя) метод во время unit test.
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/
Ответ 5
Вы можете смоделировать тестовый интерфейс, который наследуется от реального и имеет элемент с такой же сигнатурой, что и у метода расширения.
Затем вы можете смоделировать тестовый интерфейс, добавить реальный в макет и вызвать тестовый метод в настройке.
Ваша реализация макета может затем вызвать любой метод, который вы хотите, или просто проверить, что метод вызывается:
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 в этом посте за то, как реализовать макет, поддерживающий интерфейс. Как только я нашел его, адаптировать его к тестовому интерфейсу и статическому методу было очень просто.