简体   繁体   中英

Mocking generic method call for any given type parameter

I have an interface

public interface IDataProvider
{
    T GetDataDocument<T>(Guid document) where T:class, new()
}

I'd like to mock it in a way, that it would just return a new instance of a given type, regardless of the exact type, something like:

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

(which doesn't work of course, because I cannot just give any type parameter to moq, and I can't know which type must be returned.

Any ideas on this one?

Instead of using a mock, maybe your case would be better to use a Stub .

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

If you truly need a mock (so you can verify that GetDataDocument was called). Instead of trying to wrestle with a Mocking framework it sometimes is easier to just create a Mock class out right.

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();
    }
}

And than in your test:

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

For the particular test you are going to use this mock for, you probably know what T will be, right?

simply just setup the mock for that:

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

It's not really recommended to reuse the mocks anyway, so go ahead and setup mocks for the actual test at hand.

With Moq 4.13 or later you can use

  • It.IsAnyType — matches any type
  • It.IsSubtype<T> — matches T and proper subtypes of T
  • It.IsValueType — matches only value types

To get the value from the value of the generic argument or do some other operation with the original method, you can use IInvocation parameter of InvocationAction or InvocationFunc

  • setup.Callback(new InvocationAction(invocation => ...))
  • setup.Returns(new InvocationFunc(invocation => ...))

Here is an example:

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);
}));

I had a similar issue, I chose against using a stub in this situation as I did not want additions to the interface being tested to require immediate changes to the test code. ie adding a new method should not break my existing tests.

To get the mock working I added all the public type in a given assembly at runtime.

//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 )
{
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM