使用 Moq 模拟扩展方法
Posted
技术标签:
【中文标题】使用 Moq 模拟扩展方法【英文标题】:Mocking Extension Methods with Moq 【发布时间】:2011-01-18 18:03:24 【问题描述】:我有一个预先存在的界面...
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()
我的问题是,有没有一种模拟 mixin 调用的好方法?
【问题讨论】:
根据我的经验,术语 mixin 和扩展方法是分开的。在这种情况下,我会使用后者来避免混淆:P 重复:***.com/questions/562129/…. 【参考方案1】:我使用 Wrapper 来解决这个问题。创建一个包装对象并传递您的模拟方法。
参见 Paul Irwin 的 Mocking Static Methods for Unit Testing,它有很好的例子。
【讨论】:
我喜欢这个答案,因为它所说的(没有直接说)是您需要更改代码以使其可测试。这就是它的工作原理。了解在微芯片/IC/ASIC 设计中,这些芯片不仅要设计成可以工作,而且还要进一步设计成可测试的,因为如果你不能测试微芯片,它就没有用——你不能保证它会工作。软件也是如此。如果您还没有将其构建为可测试的,那么它是……没用的。将其构建为可测试的,这在某些情况下意味着重写代码(并使用包装器),然后构建测试它的自动化测试。 我创建了一个包含 Dapper、Dapper.Contrib 和 IDbConnection 的小型库。 github.com/codeapologist/DataAbstractions.Dapper【参考方案2】:您不能使用模拟框架“直接”模拟静态方法(因此是扩展方法)。您可以尝试 Moles (http://research.microsoft.com/en-us/projects/pex/downloads.aspx),这是一个来自 Microsoft 的免费工具,它实现了不同的方法。 以下是该工具的说明:
Moles 是一个轻量级框架,用于 .NET 中基于委托的测试存根和绕行。
Moles 可用于绕过任何 .NET 方法,包括密封类型中的非虚拟/静态方法。
您可以将 Moles 与任何测试框架一起使用(它是独立的)。
【讨论】:
除了 Moles,还有其他(非免费)模拟框架使用 .NET 的分析器 API 来模拟对象,因此可以替换任何调用。我认识的两个是Telerik's JustMock 和TypeMock Isolator。 Moles 理论上是好的,但我在试用时发现了三个问题,阻止了我使用它... 1)它不能在 Resharper NUnit 运行器中运行 2)您需要手动创建每个 stubbed 程序集都有一个 moole 程序集 3) 每当 stubbed 方法发生变化时,您都需要手动重新创建一个 moole 程序集。【参考方案3】:我发现我必须发现我试图模拟输入的扩展方法的内部,并模拟扩展内部发生的事情。
我认为使用扩展程序直接将代码添加到您的方法中。这意味着我需要模拟扩展内部发生的事情,而不是扩展本身。
【讨论】:
这并不总是可行的。例如。使用不是您编写的非开源扩展时。【参考方案4】:当我包装对象本身时,我喜欢使用包装器(适配器模式)。我不确定我是否会用它来包装扩展方法,它不是对象的一部分。
我使用 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/
【讨论】:
这很好,但读者应该知道_DoWorkMethod 是该类的一个新字段,现在该类的每个实例都必须再分配一个字段。这很少见,但有时它确实取决于您在任何时候分配的实例数量。您可以通过将 _DoWorkMethod 设为静态来解决此问题。这样做的缺点是,如果您同时运行单元测试,则可能会修改相同静态值的两个不同单元测试将完成。 非常精简和整洁【参考方案5】:如果您只是想确保调用了扩展方法,而不是尝试设置返回值,那么您可以检查模拟对象的 Invocations
属性。
像这样:
var invocationsCount = mockedObject.Invocations.Count;
invocationsCount.Should().BeGreaterThan(0);
【讨论】:
对于返回值,我们使用了起订量的回调功能【参考方案6】:无法模拟扩展方法的原因已经在很好的答案中给出。我只是想用这个答案给出另一种可能的解决方案:通过调用扩展方法提取受保护的虚拟方法,并使用代理在测试类/方法中为此方法创建设置。
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);
【讨论】:
【参考方案7】:因此,如果您正在使用 Moq,并且想要模拟扩展方法的结果,那么您可以在具有您尝试模拟的扩展方法的模拟类的实例上使用 SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())
。
它并不完美,但出于单元测试的目的,它运行良好。
【讨论】:
我相信是 SetReturnsDefault以上是关于使用 Moq 模拟扩展方法的主要内容,如果未能解决你的问题,请参考以下文章
使用 Moq 模拟类时,如何仅针对特定方法进行 CallBase?
Moq:设置一个模拟方法在第一次调用时失败,在第二次调用时成功