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?
MethodInfo method = typeof(Foo). GetMethod("MyGenericMethod"); method = method. MakeGenericMethod(t); method. Invoke(this, new object[0]);
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.
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.
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);
With Moq 4.13 or later you can use
It.IsAnyType
— matches any typeIt.IsSubtype<T>
— matches T and proper subtypes of TIt.IsValueType
— matches only value typesTo 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);
}));
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.
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 )
{
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With