我有一个预先存在的界面......
public interface ISomeInterface
{
void SomeMethod();
}
我已经使用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()
我的问题是,有没有一种很好的方法来模拟混音调用?
答案 0 :(得分:30)
你不能用模拟框架“直接”模拟静态方法(因此扩展方法)。您可以尝试使用Microsoft的免费工具Moles(http://research.microsoft.com/en-us/projects/pex/downloads.aspx)来实现不同的方法。 以下是该工具的说明:
Moles是.NET中基于委托的测试存根和绕道的轻量级框架。
Moles可用于绕过任何.NET方法,包括密封类型中的非虚拟/静态方法。
您可以将Moles与任何测试框架一起使用(它与此无关)。
答案 1 :(得分:16)
我使用了Wrapper来解决这个问题。创建一个包装器对象并传递模拟方法。
请参阅Paul Irwin的Mocking Static Methods for Unit Testing,它有很好的例子。
答案 2 :(得分:6)
我发现我必须发现扩展方法的内部,我试图模拟输入,并模拟扩展内部的内容。
我使用扩展程序查看直接向您的方法添加代码。这意味着我需要模拟扩展内部发生的事情而不是扩展本身。
答案 3 :(得分:6)
您可以轻松地使用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的开发人员之一。
答案 4 :(得分:2)
我喜欢在包装对象本身时使用包装器(适配器模式)。我不确定我是否会使用它来包装扩展方法,这不是对象的一部分。
我使用Action,Func,Predicate或delegate类型的内部Lazy Injectable属性,并允许在单元测试期间注入(交换)该方法。
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 :(得分:2)
您可以模拟一个测试接口,该接口继承自真实接口,并且具有与扩展方法具有相同签名的成员。
然后您可以模拟测试界面,将真实的界面添加到模拟界面中,然后在设置中调用测试方法。
然后,您的模拟实现可以调用您想要的任何方法,或者只是检查该方法是否被调用:
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
感谢this post中的HåvardS解决方案如何实现支持接口的模拟。一旦找到它,就可以通过测试界面和静态方法对其进行调整了。
答案 6 :(得分:1)
如果您只想确保调用了扩展方法,并且不想设置返回值,那么您可以检查模拟对象上的 Invocations
属性。
像这样:
var invocationsCount = mockedObject.Invocations.Count;
invocationsCount.Should().BeGreaterThan(0);
答案 7 :(得分:-1)
因此,如果您正在使用Moq,并且想模拟Extension方法的结果,则可以在具有您要模拟的扩展方法的模拟类的实例上使用SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())
。
它不是完美的,但是对于单元测试而言,它运行良好。