模拟任何给定类型参数的泛型方法调用

Posted

技术标签:

【中文标题】模拟任何给定类型参数的泛型方法调用【英文标题】:Mocking generic method call for any given type parameter 【发布时间】:2011-07-15 17:23:45 【问题描述】:

我有一个界面

public interface IDataProvider

    T GetDataDocument<T>(Guid document) where T:class, new()

我想以某种方式模拟它,它只会返回给定类型的新实例,而不考虑确切的类型,例如:

myMock.Setup(m => m.GetDataDocument<It.IsAny<Type>()>(It.IsAny<Guid>()))
.Returns(() => new T());

(这当然行不通,因为我不能只给起订量任何类型的参数,而且我不知道必须返回哪种类型。

对此有什么想法吗?

【问题讨论】:

【参考方案1】:

而不是使用模拟,也许您的情况最好使用Stub。

public class StubDataProvider : IDataProvider

    public T GetDataDocument<T>(Guid document) where T : class, new()
    
        return new T();
    

如果您真的需要模拟(这样您可以验证是否调用了 GetDataDocument)。有时,与其试图与 Mocking 框架搏斗,不如直接创建一个 Mock 类更容易。

public class MockDataProvider : IDataProvider

    private readonly Action _action;

    public MockDataProvider(Action action)
    
        _action = action;
    

    public T GetDataDocument<T>(Guid document) where T : class, new()
    
        _action();
        return new T();
    

比你的测试:

bool wasCalled = false;
IDataProvider dataProvider = new MockDataProvider(() =>  wasCalled = true; );
var aTable = dataProvider.GetDataDocument<ATable>(new Guid());
Debug.Assert(wasCalled);

【讨论】:

很好的解决方案,虽然我希望看到一些模拟/存根框架自动执行此操作 :) 我会尝试等一下,也许另一个答案会提示自动解决方案。跨度> 【参考方案2】:

对于您要使用此模拟的特定测试,您可能知道 T 是什么,对吧?

只需为此设置模拟:

myMock.Setup(m => m.GetDataDocument<MyDataClass>()>(It.IsAny<Guid>()))
   .Returns(() => new MyDataClass());

不建议重用模拟,所以继续为手头的实际测试设置模拟。

【讨论】:

不,重点是,我不知道确切的类型,我知道会使用几种类型,但是我懒得为它们都写不同的设置子句:) 还有一种情况,被模拟的方法使用的是被模拟的程序集内部的类型定义,在定义设置时不能使用。【参考方案3】:

Moq 4.13 或更高版本您可以使用

It.IsAnyType — 匹配任何类型 It.IsSubtype&lt;T&gt; — 匹配 T 和 T 的正确子类型 It.IsValueType — 仅匹配值类型

要从泛型参数的值中获取值或对原始方法进行一些其他操作,可以使用InvocationActionInvocationFuncIInvocation参数

setup.Callback(new InvocationAction(invocation =&gt; ...)) setup.Returns(new InvocationFunc(invocation =&gt; ...))

这是一个例子:

var myMock = new Mock<IDataProvider>();
myMock.Setup(m => m.GetDataDocument<It.IsAnyType>(It.IsAny<Guid>())).Returns(new InvocationFunc(invocation =>

    var type = invocation.Method.GetGenericArguments()[0];
    return Activator.CreateInstance(type);
));

【讨论】:

太棒了!顺便说一句,它也适用于接口。【参考方案4】:

我遇到了类似的问题,在这种情况下我选择不使用存根,因为我不希望对正在测试的接口进行添加以要求立即更改测试代码。即添加新方法不应破坏我现有的测试。

为了使模拟工作,我在运行时在给定程序集中添加了所有公共类型。

//This is fairly expensive so cache the types
static DummyRepository()

    foreach( var type in typeof( SomeTypeInAssemblyWithModelObjects ).Assembly.GetTypes() )
    
        if( !type.IsClass | type.IsAbstract || !type.IsPublic || type.IsGenericTypeDefinition )
        
            continue;
        

        g_types.Add( type );
    


public DummyRepository()

    MockRepository = new Mock<ISomeRepository>();

    var setupLoadBy = GetType().GetMethod( "SetupLoadBy", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod );

    foreach( var type in g_types )
    
        var loadMethod = setupLoadBy.MakeGenericMethod( type );
        loadMethod.Invoke( this, null );
    


private void SetupLoadBy<T>()

    MockRepository.Setup( u => u.Load<T>( It.IsAny<long>() ) ).Returns<long>( LoadById<T> );


public T LoadById<T>( long id )


【讨论】:

以上是关于模拟任何给定类型参数的泛型方法调用的主要内容,如果未能解决你的问题,请参考以下文章

请教关于java的泛型方法

java中的泛型

java类的泛型方法调用问题

Java的泛型方法概念原则

详解C#泛型

Java中的泛型理解