Reputation: 2029
I've got an issue setting up a mock (using moq).
We've got a generic repository:
public class Repository<T> : IRepository<T>
where T : EntityBase
{
public Repository(DbSet<T> set)
{
Set = set;
}
[...]
}
And I would like to dynamically create repositories which will be returned by our IRepositoryResolver:
public interface IRepositoryResolver
{
TRepository Resolve<TRepository, TEntity>()
where TRepository : IRepository<TEntity>
where TEntity : EntityBase;
IRepository<TEntity> ResolveFor<TEntity>()
where TEntity : EntityBase;
IRepository<TEntity> ResolveFor<TEntity>(TEntity entity)
where TEntity : EntityBase;
}
For this, I've implemented these mock setup methods:
private void SetupRepositoryResolverMock()
{
var basisDataTypes = Assembly.GetAssembly(typeof(Basisdata))
.GetTypes()
.Where(p => p.IsClass && !p.IsAbstract
&& typeof(Basisdata).IsAssignableFrom(p))
.ToList();
Basisdata[] basisdataInstances = new Basisdata[basisDataTypes.Count];
for (int i = 0; i < basisDataTypes.Count; i++)
{
basisdataInstances[i] = (Basisdata)Activator.CreateInstance(basisDataTypes.ElementAt(i));
}
_repositoryResolverMock = new Mock<IRepositoryResolver>();
foreach (var basisdataInstance in basisdataInstances)
{
Type genericRepository = typeof(Repository<>);
Type constructedRepository = genericRepository.MakeGenericType(basisdataInstance.GetType());
var repositoryInstance = Activator.CreateInstance(constructedRepository, GetQueryableMockDbSet(new[] { basisdataInstance }).Object);
//_repositoryResolverMock
// .Setup(x => x.ResolveFor(basisdataInstance))
// .Returns(() => repositoryInstance);
}
}
private static Mock<DbSet<T>> GetQueryableMockDbSet<T>(ICollection<T> sourceList) where T : EntityBase
{
[...]
}
The data model is: concrete implementations extend abstract Basisdata which extend abstract EntityBase
Now my issue is that the type passed GetQueryableMockDbSet will always return an instance of Mock<DbSet<Basisdata>>
instead of a DbSet of a concrete implementation on line var repositoryInstance = Activator.CreateInstance(constructedRepository, GetQueryableMockDbSet(new[] { basisdataInstance }).Object);
which obviously leads to an Exception as T
does not match for the repository and DBSet.
Question: How can I make GetQueryableMockDbSet return a DBset for the correct type?
Note that I want this to be dynamic and don't know all the entities extending Basisdata.
[Edit] here the test and setup methods:
[SetUp]
public void Setup()
{
SetupServiceMock();
SetupRepositoryResolverMock();
SetupPersonalModuleMock();
_onlineSyncServicePersonal = new OnlineSyncServicePersonal(_serviceMock.Object, _repositoryResolverMock.Object, new[] { _personalModuleMock.Object });
}
[Test]
public void CheckoutTest()
{
// arrange
var checkoutRequest = new CheckoutRequest
{
DienstId = Guid.NewGuid(),
OrganisationId = Guid.NewGuid(),
CheckoutId = Guid.NewGuid(),
LockModuleNames = new[]
{
Constants.ModuleName.BASE,
Constants.ModuleName.PERSONAL
}
};
// act
var checkoutResult = _onlineSyncServicePersonal.Checkout(checkoutRequest);
// assert
}
Upvotes: 5
Views: 2938
Reputation: 2398
The problem here is your use of generic methods with implicitly inferred types. Apart from Basisdata[]
all other type usages are var
and generics with <T>
which the compiler all resolves to Basisdata
. Since Moq
uses the same mechanism to define the type, instead of looking at the type of the passed object, you end up with DbSet<Basisdata>
.
You could get around this with a generic mock-builder class which you create with reflection as well. I quickly build this and tested it, tell me if it works for you too:
public class MockCreator
{
public static Mock CreateMock(Type genericType, Type itemType)
{
var typeToMock = genericType.MakeGenericType(itemType);
var creator = typeof(Mock<>).MakeGenericType(typeToMock);
return (Mock)Activator.CreateInstance(creator);
}
}
// Usage
var types = new Type[0]; // Your entity types
var sets = types.Select(t => MockCreator.CreateMock(typeof(DbSet<>), t)).ToList();
// Or in your case
var setMock = MockCreator.CreateMock(typeof(DbSet<>), basisdataInstance.GetType());
Edit: Reduced code to a single static class that can create the Mocks.
Upvotes: 11