使用Moq模拟不安全的界面

Jon*_*nny 6 c# unit-testing pointers unsafe moq

是否可以使用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();
    ...
}
Run Code Online (Sandbox Code Playgroud)

但是,我不能使用不安全的类型作为类型参数,我得到错误:

Error   1   The type 'int*' may not be used as a type argument  
Run Code Online (Sandbox Code Playgroud)

是否可以在不使用类型参数的情况下设置模拟以避免此问题?

(我知道显而易见的解决方案是不使用不安全的接口,我想知道是否有一个解决方案可以使用不安全的接口.)

编辑/更新:可以使用存根类,但我想避免使用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();
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

Pat*_*irk 5

快速回答如果您的类型在方法签名中具有指针类型,则不可能.


您看到的错误是因为您不能将指针用作类型参数.实际上,在C#规范中,您将在第4.4.1节(类型参数)中找到:

在不安全的代码中,type-argument可能不是指针类型.

您可以通过更改代码以期望特定指针来避免此特定错误:

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));
Run Code Online (Sandbox Code Playgroud)

但是,Moq在Setup调用中失败,因为它试图为参数创建一个Matcher对象(用于匹配设置和调用),该参数使用参数的类型作为类型参数.这导致与上述相同的错误.传递Match通过Match.CreateIt.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;
}
Run Code Online (Sandbox Code Playgroud)

这似乎可行,但它失败并出现异常:

System.Security.VerificationException : Operation could destabilize the runtime.
   at lambda_method(Closure)
   at Moq.MatcherFactory.CreateMatcher(Expression expression, Boolean isParams)
   at Moq.MethodCall..ctor(Mock mock, Func`1 condition, Expression originalExpression, MethodInfo method, Expression[] arguments)
   at Moq.Mock.Verify(Mock mock, Expression`1 expression, Times times, String failMessage)
   at Moq.Mock`1.Verify(Expression`1 expression)

我无法弄清楚它来自何处或如何规避它,所以这也是一个死胡同.

这里最好的情况是,如果你可以改变int*IntPtr,这通常可以嘲笑.如果您无法更改类型,那么根据您要查看的内容,您可以创建存根对象而不是依赖Moq:

unsafe class StubMyUnsafeInterface : IMyUnsafeInterface
{
    readonly int* expectedIntPtr;

    public StubMyUnsafeInterface(int* expectedIntPtr)
    {
        this.expectedIntPtr = expectedIntPtr;
    }

    public unsafe void DoWork(int* intPtr)
    {
        Assert.That(intPtr == expectedIntPtr);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在你的测试中:

int* any = stackalloc int[4];
int* other = stackalloc int[4];
var stubMyUnsafeInterface = new StubMyUnsafeInterface(any);

stubMyUnsafeInterface.DoWork(any);   // passes
stubMyUnsafeInterface.DoWork(other); // fails
Run Code Online (Sandbox Code Playgroud)