是否可以使用 Moq 来模拟不安全的接口?例如我有(MCVE):
[TestClass]
public class UnitTest1
{
[TestMethod]
public unsafe void TestMethod1()
{
Mock<IMyUnsafeInterface> mockDependency = new Mock<IMyUnsafeInterface>();
mockDependency.Setup(i => i.DoWork(It.IsAny<int*>())); // error on this line
systemUnderTest.Dependency = mockDependency.Object;
...
}
}
public unsafe interface IMyUnsafeInterface
{
void DoWork(int* intPtr);
byte* MethodNotRelevantToThisTest1(byte* bytePtr);
ComplexObject MethodNotRelevantToThisTest2();
...
}
但是我不能使用不安全的类型作为类型参数,我得到了错误:
Error 1 The type 'int*' may not be used as a type argument
是否可以在不使用类型参数的情况下设置模拟来避免这个问题?
(我知道明显的解决方案是不使用不安全的接口,我想知道是否有解决方案可以处理不安全的接口。)
编辑/更新:可以使用存根类,但如果可以使用 Moq,我想避免这种情况,因为 Moq 提供的代码要少得多。
public unsafe class MyUnsafeClass : IMyUnsafeInterface
{
public void DoWork(int* intPtr) {
// Do something
}
public byte* MethodNotRelevantToThisTest1(byte* bytePtr) {
throw new NotImplementedException();
}
public ComplexObject MethodNotRelevantToThisTest2() {
throw new NotImplementedException();
}
...
}
快速回答如果你的类型在方法签名中有指针类型是不可能的。
你看到的错误是因为你不能使用指针作为类型参数。实际上,在 C# 规范 中,您会在第 4.4.1 节(类型参数)中找到:
在不安全的代码中,类型参数可能不是指针类型。
您可以通过更改代码以期望特定指针来避免此特定错误:
Mock<IMyUnsafeInterface> mockDependency = new Mock<IMyUnsafeInterface>();
mockDependency.Setup(i => i.DoWork(any));
// use the mock
mockDependency.Object.DoWork(any);
mockDependency.Verify(p => p.DoWork(any));
但是,Moq 在
Setup
调用中失败,因为它试图为参数创建一个 Matcher
对象(用于将设置与调用匹配),该对象使用参数的类型作为类型参数。这会导致与上述相同的错误。传递通过 Match
或 Match.Create
方法创建的您自己的 It.Is
对象将不起作用,因为这些方法也采用类型参数。
如果您省略
Setup
调用,利用松散的模拟行为,则 Moq 会因同样的问题而在 Verify
调用中失败。它试图根据参数参数的类型创建一个对象,以便它可以将记录的调用与传入的表达式相匹配。
Moq 还在提供
It
类之前提供了一种较旧的匹配参数的方法,您可以在其中使用 Matcher
属性标记方法:
[Test]
public unsafe void TestMethod1()
{
int* any = stackalloc int[4];
Mock<IMyUnsafeInterface> mockDependency = new Mock<IMyUnsafeInterface>();
// use the mock
mockDependency.Object.DoWork(any);
mockDependency.Verify(p => p.DoWork(Is(any)));
}
[Matcher]
public static unsafe int* Is(int* expected) { return null; }
public static unsafe bool Is(int* actual, int* expected)
{
return actual == expected;
}
这似乎可行,但它失败了,但有一个例外:
System.Security.VerificationException:操作可能会破坏运行时的稳定性。 在 lambda_method(闭包) 在 Moq.MatcherFactory.CreateMatcher(表达式表达式,布尔值 isParams) 在 Moq.MethodCall..ctor(模拟模拟,Func`1 条件,表达式 originalExpression,MethodInfo 方法,表达式 [] 参数) 在 Moq.Mock.Verify(Mock 模拟,Expression`1 表达式,Times times,String failMessage) 在 Moq.Mock`1.Verify(Expression`1 expression)
我不太清楚这是从哪里来的或如何规避它,所以这也是一个死胡同。
这里最好的情况是,如果您可以将
int*
更改为 IntPtr
,这可以正常模拟。如果您不能更改类型,那么根据您要验证的内容,您可以制作一个存根对象而不是依赖起订量:
unsafe class StubMyUnsafeInterface : IMyUnsafeInterface
{
readonly int* expectedIntPtr;
public StubMyUnsafeInterface(int* expectedIntPtr)
{
this.expectedIntPtr = expectedIntPtr;
}
public unsafe void DoWork(int* intPtr)
{
Assert.That(intPtr == expectedIntPtr);
}
}
然后在你的测试中:
int* any = stackalloc int[4];
int* other = stackalloc int[4];
var stubMyUnsafeInterface = new StubMyUnsafeInterface(any);
stubMyUnsafeInterface.DoWork(any); // passes
stubMyUnsafeInterface.DoWork(other); // fails
以下是我针对这种情况使用的方法的文字说明。但是,向下滚动到代码并查看其工作原理可能更容易:
创建一个新接口(派生自有问题的接口),它声明每个采用或返回非托管指针的方法的重载,并将其替换为
IntPtr
(因为 IntPtr 可由 Moq 模拟)。
(注意:如果有一个方法仅在返回类型上有所不同,那么我只是稍微更改名称——例如,我更改了
Foo()
to FooIntPtr
用一个类实现新接口(它的构造函数接受上面创建的新接口)。
一个。对于此类中的每个有问题的方法(那些具有非托管指针的方法),让它将调用委托给基于 IntPtr 的方法(通过进行适当的转换。
b.对于所有其他方法,只需将它们转发到通过构造函数传入的接口即可。
在你的测试中:
一个。使用 Moq 模拟新创建的界面版本。 b.模拟
IntPtr
版本的方法。这样就可以继续使用Moq来mock所有的方法了
[TestClass]
public class UnitTest1
{
[TestMethod]
public unsafe void TestMethod1()
{
Mock<IMyUnsafeInterfaceForMoq> mockDependency = new Mock<IMyUnsafeInterfaceForMoq>();
mockDependency.Setup(i => i.DoWork(It.IsAny<IntPtr>())); // use IntPtr instead of int*
// Use this wrapper whenever passing it to the system under test:
var dependency = new MyUnsafeInterfaceMoqWrapper(mockDependency.Object);
var systemUnderTest = new SystemUnderTest();
systemUnderTest.Dependency = dependency;
}
}
public unsafe interface IMyUnsafeInterface
{
void DoWork(int* intPtr);
byte* MethodNotRelevantToThisTest1(byte* bytePtr);
bool MethodNotRelevantToThisTest2();
}
// Interface to work around unsafe pointer types by using IntPtr
internal interface IMyUnsafeInterfaceForMoq : IMyUnsafeInterface
{
void DoWork(IntPtr intPtr);
IntPtr MethodNotRelevantToThisTest1(IntPtr bytePtr);
}
// Wrapper to workaround unsafe pointer types by using IntPtr
sealed class MyUnsafeInterfaceMoqWrapper : IMyUnsafeInterfaceForMoq
{
readonly IMyUnsafeInterfaceForMoq _wrapped;
public MyUnsafeInterfaceMoqWrapper(IMyUnsafeInterfaceForMoq wrapped)
{
_wrapped = wrapped ?? throw new ArgumentNullException(nameof(wrapped));
}
// For the methods that don't work with Moq, delegate to the methods that *do* work with moq
// and cast appropriately.
public unsafe void DoWork(int* intPtr) => DoWork((IntPtr)intPtr);
public unsafe byte* MethodNotRelevantToThisTest1(byte* bytePtr) => (byte*)MethodNotRelevantToThisTest1((IntPtr)bytePtr);
// The rest of the methods just forward on.
public void DoWork(IntPtr intPtr) => _wrapped.DoWork(intPtr);
public IntPtr MethodNotRelevantToThisTest1(IntPtr bytePtr) => _wrapped.MethodNotRelevantToThisTest1(bytePtr);
public bool MethodNotRelevantToThisTest2() => _wrapped.MethodNotRelevantToThisTest2();
}
class SystemUnderTest
{
public IMyUnsafeInterface Dependency { get; set; }
}
为避免这种情况,您可以尝试使用 Wrapper 类概念。您可以简单地包装您的原始类或接口。之后可以在 Wrapper 类函数中使用您的原始函数。如下例 -
//Wrapper class
public class MyWrapperClass
{
public YourReturnType MyFunction()
{
OriginalClass obj = new OriginalClass();
//And inside this function call your original function
return obj.OriginalFunction();
}
}
Wrapper 类包装了原始类型,我们可以根据需要使用/模拟原始对象。
如果你不知道 Wrapper 类的概念,那么首先要了解它的概念。
http://www.c-sharpcorner.com/Blogs/12038/wrapper-class-in-C-Sharp.aspx