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 开发者_JS百科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);
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 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);
}));
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 )
{
}
精彩评论