【问题标题】: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());

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

对此有什么想法吗?

【问题讨论】:

    标签: c# moq


    【解决方案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 )
    {
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多