简体   繁体   中英

EF6 Mocking derived DbSets

I am trying to apply the new mocking of EF6 to my existing code.

I have a class that Extends DbSet. One of the methods call the base class (BdSet) Create method. Here is the a sample code (not the complete solution or real names):

public class DerivedDbSet<TEntity> : DbSet<TEntity>, IKeyValueDbSet<TEntity>, IOrderedQueryable<TEntity> where TEntity : class
{
    public virtual bool Add(string value1, string value2) {
        var entity = Create(); // There is no direct implementation of the Create method it is calling the base method
        // Do something with the values
        this.Add(entity);
        return true;
    }
}

I am mocking using the Test Doubles sample (here is the peace of code):

var data = new List<DummyEntity> {
    new DummyEntity { Value1 = "First", Value2 = "001" },
    new DummyEntity { Value1 = "Second", Value2 = "002" }
}.AsQueryable();
var mock = new Mock<DerivedDbSet<DummyEntity>>();
mock.CallBase = true;
mock.As<IQueryable<T>>().Setup(m => m.Provider).Returns(source.Provider);
mock.As<IQueryable<T>>().Setup(m => m.Expression).Returns(source.Expression);
mock.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(source.ElementType);
mock.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator());

I've set the CallBase property to true to try to force the call to the base class...

But I keep receiving the following error:

System.NotImplementedException: The member 'Create' has not been implemented on type 'DerivedDbSet 1Proxy' which inherits from 'DbSet 1'. Test doubles for 'DbSet`1' must provide implementations of methods and properties that are used.

I want the call of create to fallback to the default implementation in DbSet.

Can someone help me with that?

After some struggle with the internal functions and async references of mocking a DbSet I came out with a helper class that solved most of my problems and might serve as base to someone implementation. Here is the code:

public static class MockHelper
{
    internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider {

        private readonly IQueryProvider _inner;

        internal TestDbAsyncQueryProvider(IQueryProvider inner) { _inner = inner; }
        public IQueryable CreateQuery(Expression expression) { return new TestDbAsyncEnumerable<TEntity>(expression); }
        public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { return new TestDbAsyncEnumerable<TElement>(expression); }
        public object Execute(Expression expression) { return _inner.Execute(expression); }
        public TResult Execute<TResult>(Expression expression) { return _inner.Execute<TResult>(expression); }
        public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken) { return Task.FromResult(Execute(expression)); }
        public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) { return Task.FromResult(Execute<TResult>(expression)); }
    }

    internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T> {
        public TestDbAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { }
        public TestDbAsyncEnumerable(Expression expression) : base(expression) { }
        public IDbAsyncEnumerator<T> GetAsyncEnumerator() { return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); }
        IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() { return GetAsyncEnumerator(); }
        public IQueryProvider Provider { get { return new TestDbAsyncQueryProvider<T>(this); } }
    } 

    internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> {

        private readonly IEnumerator<T> _inner;

        public TestDbAsyncEnumerator(IEnumerator<T> inner) { _inner = inner; }
        public void Dispose() { _inner.Dispose(); }
        public Task<bool> MoveNextAsync(CancellationToken cancellationToken) { return Task.FromResult(_inner.MoveNext()); }
        public T Current { get { return _inner.Current; } }
        object IDbAsyncEnumerator.Current { get { return Current; } }
    } 

    public static Mock<TDbSet> CreateDbSet<TDbSet, TEntity>(IList<TEntity> data, Func<object[], TEntity> find = null)
        where TDbSet : class, IDbSet<TEntity>
        where TEntity : class, new() {
        var source = data.AsQueryable();
        var mock = new Mock<TDbSet> { CallBase = true };
        mock.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(source.Expression);
        mock.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(source.ElementType);
        mock.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator());
        mock.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(source.Provider));
        mock.As<IDbAsyncEnumerable<TEntity>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator()));
        mock.As<IDbSet<TEntity>>().Setup(m => m.Create()).Returns(new TEntity());
        mock.As<IDbSet<TEntity>>().Setup(m => m.Add(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Add(i); return i; });
        mock.As<IDbSet<TEntity>>().Setup(m => m.Remove(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Remove(i); return i; });
        if (find != null) mock.As<IDbSet<TEntity>>().Setup(m => m.Find(It.IsAny<object[]>())).Returns(find);
        return mock;
    }

    public static Mock<DbSet<TEntity>> CreateDbSet<TEntity>(IList<TEntity> data, Func<object[], TEntity> find = null)
        where TEntity : class, new() {
        return CreateDbSet<DbSet<TEntity>, TEntity>(data, find);
    }
}

And here is a use sample (based on the names I've given before):

var data = new List<DummyEntity> {
    new DummyEntity { Value1 = "First", Value2 = "001" } },
    new DummyEntity { Value1 = "Second", Value2 = "002" } }
};
var mockDummyEntities = MockHelper.CreateDbSet<DerivedDbSet<DummyEntities>, DummyEntities>(data, i => data.FirstOrDefault(k => k.Value2 == (string)i[0]));
var mockContext = new Mock<DummyDbContext>();
mockContext.Setup(c => c.DummyEntities).Returns(mockDummyEntities.Object);

Any suggestions on how to improve this solution is very welcome.

Regards

This is a modified version based on Andre that worked for me. Note, that I did not need the async references. Code will add all derived classes (if any)

Usage:

/// <summary>
/// 
/// </summary>
[TestMethod]
public void SomeTest()
{
    //Setup
    var mockContext = new Mock<FakeDbContext>();
    //SomeClass can be abstract or concrete
    mockContext.createFakeDBSet<SomeClass>();
    var db = mockContext.Object;

    //Setup create(s) if needed on concrete classes
    //Mock.Get(db.Set<SomeOtherClass>()).Setup(x => x.Create()).Returns(new SomeOtherClass());

    //DO Stuff

    var list1 = db.Set<SomeClass>().ToList();
    //SomeOtherClass derived from SomeClass
    var subList1 = db.Set<SomeOtherClass>().ToList();
    CollectionAssert.AreEquivalent(list1.OfType<SomeOtherClass>.ToList(), subList1);
}

Code:

/// <summary>
/// http://stackoverflow.com/questions/21943328/ef6-mocking-derived-dbsets
/// </summary>
public static class MoqSetupExtensions
{
    static IEnumerable<Type> domainTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes());
    public static Mock<DbSet<T>> createFakeDBSet<T>(this Mock<FakeDbContext> db, List<T> list = null, Func<List<T>, object[], T> find = null, bool createDerivedSets = true) where T : class
    {
        list = list ?? new List<T>();
        var data = list.AsQueryable();
        //var mockSet = MockHelper.CreateDbSet(list, find);
        var mockSet = new Mock<DbSet<T>>() { CallBase = true };
        mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(() => { return data.Provider; });
        mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(() => { return data.Expression; });
        mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(() => { return data.ElementType; });
        mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => { return list.GetEnumerator(); });

        mockSet.Setup(m => m.Add(It.IsAny<T>())).Returns<T>(i => { list.Add(i); return i; });
        mockSet.Setup(m => m.AddRange(It.IsAny<IEnumerable<T>>())).Returns<IEnumerable<T>>((i) => { list.AddRange(i); return i; });
        mockSet.Setup(m => m.Remove(It.IsAny<T>())).Returns<T>(i => { list.Remove(i); return i; });
        if (find != null) mockSet.As<IDbSet<T>>().Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>((i) => { return find(list, i); });
        //mockSet.Setup(m => m.Create()).Returns(new T());

        db.Setup(x => x.Set<T>()).Returns(mockSet.Object);

        //Setup all derived classes            
        if (createDerivedSets)
        {
            var type = typeof(T);
            var concreteTypes = domainTypes.Where(x => type.IsAssignableFrom(x) && type != x).ToList();
            var method = typeof(MoqSetupExtensions).GetMethod("createFakeDBSetSubType");
            foreach (var item in concreteTypes)
            {
                var invokeResult = method.MakeGenericMethod(type, item)
                    .Invoke(null, new object[] { db, mockSet });
            }
        }
        return mockSet;
    }
    public static Mock<DbSet<SubType>> createFakeDBSetSubType<BaseType, SubType>(this Mock<FakeDbContext> db, Mock<DbSet<BaseType>> baseSet)
        where BaseType : class
        where SubType : class, BaseType
    {
        var dbSet = db.Object.Set<BaseType>();

        var mockSet = new Mock<DbSet<SubType>>() { CallBase = true };
        mockSet.As<IQueryable<SubType>>().Setup(m => m.Provider).Returns(() => { return dbSet.OfType<SubType>().Provider; });
        mockSet.As<IQueryable<SubType>>().Setup(m => m.Expression).Returns(() => { return dbSet.OfType<SubType>().Expression; });
        mockSet.As<IQueryable<SubType>>().Setup(m => m.ElementType).Returns(() => { return dbSet.OfType<SubType>().ElementType; });
        mockSet.As<IQueryable<SubType>>().Setup(m => m.GetEnumerator()).Returns(() => { return dbSet.OfType<SubType>().GetEnumerator(); });

        mockSet.Setup(m => m.Add(It.IsAny<SubType>())).Returns<SubType>(i => { dbSet.Add(i); return i; });
        mockSet.Setup(m => m.AddRange(It.IsAny<IEnumerable<SubType>>())).Returns<IEnumerable<SubType>>((i) => { dbSet.AddRange(i); return i; });
        mockSet.Setup(m => m.Remove(It.IsAny<SubType>())).Returns<SubType>(i => { dbSet.Remove(i); return i; });
        mockSet.As<IDbSet<SubType>>().Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>((i) => { return dbSet.Find(i) as SubType; });

        baseSet.Setup(m => m.Create<SubType>()).Returns(() => { return mockSet.Object.Create(); });

        db.Setup(x => x.Set<SubType>()).Returns(mockSet.Object);
        return mockSet;
    }
}

Microsoft published a very well explained guide providing an in-memory implementation of DbSet which has a complete implemention for all the methods on DbSet.

Check the article at http://msdn.microsoft.com/en-us/data/dn314431.aspx#doubles

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM