简体   繁体   English

EF6 Mocking派生DbSets

[英]EF6 Mocking derived DbSets

I am trying to apply the new mocking of EF6 to my existing code. 我正在尝试将新的模拟EF6应用到我现有的代码中。

I have a class that Extends DbSet. 我有一个扩展DbSet的类。 One of the methods call the base class (BdSet) Create method. 其中一个方法调用基类(BdSet)Create方法。 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): 我正在嘲笑使用Test Doubles样本(这是代码的和平):

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... 我已将CallBase属性设置为true以尝试强制调用基类...

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'. System.NotImplementedException:成员'Create'尚未在1Proxy' which inherits from 'DbSet 1'的类型'DerivedDbSet 1Proxy' which inherits from 'DbSet上实现。 Test doubles for 'DbSet`1' must provide implementations of methods and properties that are used. 'DbSet`1'的测试双精度必须提供所使用的方法和属性的实现。

I want the call of create to fallback to the default implementation in DbSet. 我希望create的调用回退到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. 在使用内部函数和模拟DbSet的异步引用进行了一些讨论之后,我找到了一个帮助类,它解决了我的大多数问题,并可能作为某个实现的基础。 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. 这是基于Andre的修改版本,对我有用。 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. Microsoft发布了一个非常好的解释指南,提供了DbSet的内存实现,它完全实现了DbSet上的所有方法。

Check the article at http://msdn.microsoft.com/en-us/data/dn314431.aspx#doubles 查看http://msdn.microsoft.com/en-us/data/dn314431.aspx#doubles上的文章

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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