Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking generic method call for any given type parameter

Tags:

c#

moq

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?

like image 611
Hassan Avatar asked Mar 15 '11 11:03

Hassan


People also ask

How do you call a generic method in C#?

MethodInfo method = typeof(Foo). GetMethod("MyGenericMethod"); method = method. MakeGenericMethod(t); method. Invoke(this, new object[0]);

What is generic type parameter in C#?

The type parameter is a placeholder for a specific type that the client specifies when they create an instance of the generic type. A generic class cannot be used as-is because it is simply a blueprint for that type.

What does MOQ setup do?

Moq provides a library that makes it simple to set up, test, and verify mocks. We can start by creating an instance of the class we're testing, along with a mock of an interface we want to use.


4 Answers

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);
like image 115
Mark Coleman Avatar answered Sep 27 '22 03:09

Mark Coleman


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 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);
}));
like image 34
Vijay Nirmal Avatar answered Sep 25 '22 03:09

Vijay Nirmal


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.

like image 40
Mikael Östberg Avatar answered Sep 26 '22 03:09

Mikael Östberg


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. i.e. 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 )
{
}
like image 29
Andre Avatar answered Sep 29 '22 03:09

Andre