EF6 Mocking派生DbSets

时间:2014-02-21 19:18:37

标签: c# unit-testing mocking entity-framework-6

我正在尝试将新的模拟EF6应用于我现有的代码。

我有一个扩展DbSet的类。其中一个方法调用基类(BdSet)Create方法。这是一个示例代码(不是完整的解决方案或真实姓名):

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;
    }
}

我正在嘲笑使用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());

我已将CallBase属性设置为true以尝试强制调用基类...

但我一直收到以下错误:

  

System.NotImplementedException:成员'Create'尚未在类型'DerivedDbSet 1Proxy' which inherits from 'DbSet 1'上实现。 'DbSet`1'的测试双精度必须提供所使用的方法和属性的实现。

我希望create的调用回退到DbSet中的默认实现。

有人可以帮我吗?

3 个答案:

答案 0 :(得分:10)

在对内部函数和模拟DbSet的异步引用进行了一些努力之后,我找到了一个帮助类,它解决了我的大多数问题,并可能作为某个实现的基础。 这是代码:

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);
    }
}

这是一个使用示例(基于我之前给出的名称):

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);

非常欢迎任何关于如何改进此解决方案的建议。

此致

答案 1 :(得分:5)

这是基于Andre的修改版本,对我有用。请注意,我不需要异步引用。代码将添加所有派生类(如果有)

用法:

/// <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);
}

代码:

/// <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;
    }
}

答案 2 :(得分:2)

Microsoft发布了一个非常好的解释指南,提供了DbSet的内存实现,它完全实现了DbSet上的所有方法。

查看http://msdn.microsoft.com/en-us/data/dn314431.aspx#doubles

上的文章