C# 如何使用Moq模拟扩展方法?
我正在编写一个依赖于扩展方法结果的测试,但我不希望该扩展方法将来的失败会破坏这个测试。模拟这个结果似乎是一个显而易见的选择,但Moq似乎并没有提供一种覆盖静态方法的方法(对扩展方法的要求)。Moq.Protected和Moq.Stub也有类似的想法,但它们似乎没有为这种场景提供任何东西。我是错过了什么,还是应该换一种方式 下面是一个简单的例子,它失败于通常的“对不可重写成员的无效期望”。这是一个需要模拟扩展方法的坏例子,但它应该这样做C# 如何使用Moq模拟扩展方法?,c#,mocking,moq,extension-methods,C#,Mocking,Moq,Extension Methods,我正在编写一个依赖于扩展方法结果的测试,但我不希望该扩展方法将来的失败会破坏这个测试。模拟这个结果似乎是一个显而易见的选择,但Moq似乎并没有提供一种覆盖静态方法的方法(对扩展方法的要求)。Moq.Protected和Moq.Stub也有类似的想法,但它们似乎没有为这种场景提供任何东西。我是错过了什么,还是应该换一种方式 下面是一个简单的例子,它失败于通常的“对不可重写成员的无效期望”。这是一个需要模拟扩展方法的坏例子,但它应该这样做 public class SomeType { in
public class SomeType {
int Id { get; set; }
}
var ListMock = new Mock<List<SomeType>>();
ListMock.Expect(l => l.FirstOrDefault(st => st.Id == 5))
.Returns(new SomeType { Id = 5 });
公共类SomeType{
int Id{get;set;}
}
var ListMock=new Mock();
Expect(l=>l.FirstOrDefault(st=>st.Id==5))
.Returns(新的SomeType{Id=5});
对于那些TypeMock的瘾君子,他们可能会建议我改用隔离器:我很感谢他们的努力,因为看起来TypeMock可以蒙住眼睛,醉醺醺地完成这项工作,但我们的预算不会很快增加。扩展方法只是变相的静态方法。Moq或rhinomock等模拟框架只能创建对象的模拟实例,这意味着模拟静态方法是不可能的。我知道这个问题已经有一年没有出现了,但微软发布了一个框架来处理这个问题 这里还有一些教程:
public static class MyExtensions
{
public static string MyExtension<T>(this T obj)
{
return "Hello World!";
}
}
public interface IExtensionMethodsWrapper
{
string MyExtension<T>(T myObj);
}
public class ExtensionMethodsWrapper : IExtensionMethodsWrapper
{
public string MyExtension<T>(T myObj)
{
return myObj.MyExtension();
}
}
公共静态类MyExtensions
{
公共静态字符串MyExtension(此T对象)
{
返回“你好,世界!”;
}
}
公共接口IExtensionMethodsRapper
{
字符串MyExtension(T myObj);
}
公共类ExtensionMethodsWrapper:IExtensionMethodsWrapper
{
公共字符串MyExtension(T myObj)
{
返回myObj.MyExtension();
}
}
然后,您可以在测试中模拟包装器方法,并使用IOC容器编写代码。如果您可以更改扩展方法代码,则可以这样编写,以便能够进行测试:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
public static class MyExtensions
{
public static IMyImplementation Implementation = new MyImplementation();
public static string MyMethod(this object obj)
{
return Implementation.MyMethod(obj);
}
}
public interface IMyImplementation
{
string MyMethod(object obj);
}
public class MyImplementation : IMyImplementation
{
public string MyMethod(object obj)
{
return "Hello World!";
}
}
因此,扩展方法只是实现接口的包装器
(您可以只使用实现类,而不使用扩展方法,扩展方法是一种语法糖。)
您可以模拟实现接口并将其设置为extensions类的实现
public class MyClassUsingExtensions
{
public string ReturnStringForObject(object obj)
{
return obj.MyMethod();
}
}
[TestClass]
public class MyTests
{
[TestMethod]
public void MyTest()
{
// Given:
//-------
var mockMyImplementation = new Mock<IMyImplementation>();
MyExtensions.Implementation = mockMyImplementation.Object;
var myClassUsingExtensions = new MyClassUsingExtensions();
// When:
//-------
var myObject = new Object();
myClassUsingExtensions.ReturnStringForObject(myObject);
//Then:
//-------
// This would fail because you cannot test for the extension method
//mockMyImplementation.Verify(m => m.MyMethod());
// This is success because you test for the mocked implementation interface
mockMyImplementation.Verify(m => m.MyMethod(myObject));
}
}
公共类MyClassUsingExtensions
{
公共字符串ReturnStringForObject(对象obj)
{
返回obj.MyMethod();
}
}
[测试类]
公共类MyTests
{
[测试方法]
公共无效MyTest()
{
//鉴于:
//-------
var mockMyImplementation=new Mock();
MyExtensions.Implementation=mockMyImplementation.Object;
var myClassUsingExtensions=新的myClassUsingExtensions();
//当:
//-------
var myObject=新对象();
myClassUsingExtensions.ReturnStringForObject(myObject);
//然后:
//-------
//这将失败,因为您无法测试扩展方法
//验证(m=>m.MyMethod());
//这是成功的,因为您测试了模拟的实现接口
验证(m=>m.MyMethod(myObject));
}
}
对于扩展方法,我通常使用以下方法:
public static class MyExtensions
{
public static Func<int,int, int> _doSumm = (x, y) => x + y;
public static int Summ(this int x, int y)
{
return _doSumm(x, y);
}
}
公共静态类MyExtensions
{
公共静态函数(x,y)=>x+y;
公共静态整数总和(此整数x,整数y)
{
返回_doSumm(x,y);
}
}
它允许相当容易地注入_doSumm。您可以做的最好的事情是为具有扩展方法的类型提供自定义实现,例如:
[Fact]
public class Tests
{
public void ShouldRunOk()
{
var service = new MyService(new FakeWebHostEnvironment());
// Service.DoStuff() internally calls the SomeExtensionFunction() on IWebHostEnvironment
// Here it works just fine as we provide a custom implementation of that interface
service.DoStuff().Should().NotBeNull();
}
}
public class FakeWebHostEnvironment : IWebHostEnvironment
{
/* IWebHostEnvironment implementation */
public bool SomeExtensionFunction()
{
return false;
}
}
可以在此处找到重复项:。此问题比该问题早一整年。如果存在重复,则相反。2019年仍然没有合适的解决方案@TanvirArjel实际上,多年来,您可以使用它来模拟扩展方法。这和模仿任何其他方法一样简单。这里有一个到文档的链接:我刚刚尝试了这个方法,但是在将扩展所用于的父模拟对象传递到另一个程序集中的其他方法时出现了问题。在这种情况下,即使使用这种技术,当在另一个程序集中调用该方法时,也会选择原始扩展,这有点像范围问题。如果我直接在单元测试项目中调用,这也没关系,但是当您测试调用扩展方法的其他代码时,它就没有帮助了。@Stephen不知道如何避免这种情况。我从未有过这样的范围界定issue@Mendelt..Then一个单元如何测试一个内部有扩展方法的方法?什么是可能的替代方案?@Alexander我问了同样的问题,然后这个问题得到了惊人的回答:-看看这个,它是一个救生圈!这是一种变通方法,您不能再在代码中使用扩展方法语法。但是,当您无法更改扩展方法类时,它会有所帮助。@informatorius这是什么意思?在代码中使用MyExtensions类中的MyExtension()。在测试中,使用提供参数的ExtensionMethodsWrapper()类中的MyExtension()。如果测试中的类使用MyExtensions中的MyExtension(),则我无法模拟MyExtensions。因此,被测试的类必须使用IExtensionMethodsRapper,以便对其进行模拟。但是被测试的类不能再使用扩展方法语法了。这就是操作的要点。这是一个解决方法。