Philippe
Philippe

Reputation: 2029

Create Moq instances for generic types

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

Answers (1)

Toxantron
Toxantron

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

Related Questions