'Dynamically mock properties for unit testing

So, I have built a "db context" like EF but for MongoDb, instead of DbSets I have some properties of type IMongoCollection<>. My problem is testing that all properties return the correct collection, for the exception I iterate every property that I found with reflection and I call it, but when I try to mock with moq the IMongoDatabase that returns the IMongoCollection<> I always have this exception

System.NotSupportedException: Unsupported expression: ... => ....Invoke(db, new[] { It.Is(s => s == type.Name), It.Is...

System.NotSupportedException Unsupported expression: ... => ....Invoke(db, new[] { It.Is(s => s == type.Name), It.Is(s => s == null) }) Non-overridable members (here: MethodBase.Invoke) may not be used in setup / verification expressions. at Moq.Guard.IsOverridable(MethodInfo method, Expression expression) in C:\projects\moq4\src\Moq\Guard.cs:line 99

My code

public class GufoDbContextTests : IDisposable
{
    private readonly GufoDbContext _dbContext;

    private readonly Mock<IMongoClient> _mongoClientMock;

    private readonly PropertyInfo[] _collections;

    public GufoDbContextTests()
    {
        _collections = typeof(GufoDbContext).GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.CanRead)
            .Where(p => p.PropertyType.GetGenericTypeDefinition() == typeof(IMongoCollection<>))
            .ToArray();

        _mongoClientMock = new Mock<IMongoClient>();

        _dbContext = new GufoDbContext(_mongoClientMock.Object);
    }

    [Fact]
    public void AllCollections_Success_WhenDatabaseIsConnected()
    {
        var mongoDatabaseMock = new Mock<IMongoDatabase>();

        _mongoClientMock.Setup(client =>
                client.GetDatabase(It.Is<string>(c => c == DatabaseConsts.Database),
                    It.IsAny<MongoDatabaseSettings>()))
            .Returns(mongoDatabaseMock.Object);

        foreach (var collection in _collections)
        {
            var type = collection.PropertyType.GetGenericArguments().First();

            var returnType =
                (Mock)Activator.CreateInstance(
                    typeof(Mock<>).MakeGenericType(
                        typeof(IMongoCollection<>).MakeGenericType(type)
                    )
                )!;

            mongoDatabaseMock.Setup(db =>
                    db.GetType()
                        .GetMethod(nameof(IMongoDatabase.GetCollection))!
                        .MakeGenericMethod(type)
                        .Invoke(db, new object[]
                        {
                            It.Is<string>(s => s == type.Name),
                            It.Is<MongoCollectionSettings>(s => s == null)
                        })
                    )
                .Returns(returnType.Object);
        }

        _dbContext.OpenConnection(Guid.NewGuid().ToString());

        foreach (var collection in _collections)
        {
            Assert.NotNull(collection.GetValue(_dbContext, null));
        }

        foreach (var collection in _collections)
        {
            var type = collection.PropertyType.GetGenericArguments().First();

            mongoDatabaseMock.Verify(db =>
                    db.GetType()
                        .GetMethod(nameof(db.GetCollection))!
                        .MakeGenericMethod(type)
                        .Invoke(db, new object[]
                        {
                            It.Is<string>(s => s == type.Name),
                            It.IsAny<MongoCollectionSettings>()
                        }),
                Times.Once
            );
        }

        _mongoClientMock.Verify(client =>
                client.GetDatabase(It.Is<string>(c => c == DatabaseConsts.Database),
                    It.IsAny<MongoDatabaseSettings>()),
            Times.Once);

        mongoDatabaseMock.VerifyNoOtherCalls();
    }

    /// <inheritdoc />
    public void Dispose()
    {
        GC.SuppressFinalize(this);

        _mongoClientMock.VerifyNoOtherCalls();
    }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source