Entity Framework 6 模拟 DBSet 的 Include 方法

23

一直在谷歌上搜索如何模拟EF6中的DBSet的include方法的解决方案。

这个问题在这里有很好地记录:

http://entityframework.codeplex.com/discussions/461731

不幸的是,似乎那里并没有一个有效的解决办法。

有人找到了解决方法吗?

我知道我们实际上不应该模拟EF6上下文,但项目负责人坚持这么做。

提前感谢。


哪个模拟框架?你试过“Fakes框架”吗? - daryal
抱歉。使用Rhino Mocks进行模拟。 - user486523
等等,你是在嘲笑DbContext还是DbSet? - Paul D'Ambra
3个回答

25

我遇到了与@GetFuzzy相同的问题 - 似乎无论我做什么,每当在Moq DbSet上进行Include()调用时都无法避免NullReferenceException。不幸的是,另一个答案中的Github示例并没有起作用:Set.Include()始终返回null。

经过一番摆弄,我想出了一个解决方法。

[Test]
public void CanUseIncludeWithMocks()
{
    var child = new Child();
    var parent = new Parent();
    parent.Children.Add(child);

    var parents = new List<Parent> { parent };
    var children = new List<Child> { child };

    var parentsDbSet1 = new FakeDbSet<Parent>();
    parentsDbSet1.SetData(parents);

    var parentsDbSet2 = new FakeDbSet<Parent>();
    parentsDbSet2.SetData(parents);

    parentsDbSet1.Setup(x => x.Include(It.IsAny<string>())).Returns(parentsDbSet2.Object);

    // Can now test a method that does something like: context.Set<Parent>().Include("Children") etc
}


public class FakeDbSet<T> : Mock<DbSet<T>> where T : class
{
    public void SetData(IEnumerable<T> data)
    {
        var mockDataQueryable = data.AsQueryable();

        As<IQueryable<T>>().Setup(x => x.Provider).Returns(mockDataQueryable.Provider);
        As<IQueryable<T>>().Setup(x => x.Expression).Returns(mockDataQueryable.Expression);
        As<IQueryable<T>>().Setup(x => x.ElementType).Returns(mockDataQueryable.ElementType);
        As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(mockDataQueryable.GetEnumerator());
    }
}

我真的不喜欢必须有两个虚假的DbSets,但出于某种原因,这不起作用:

parentsDbSet1.Setup(x => x.Include(It.IsAny<string>())).Returns(parentsDbSet1.Object);

有人能解释一下这个吗?

9
set.Setup(s => s.Include(It.IsAny<string>())).Returns(set.Object); 对我来说可行。 - Sam
5
我不确定,但是使用Lambda表达式时s.Include(It.IsAny<string>())确实起作用。太棒了! - krillgar
2
我还在FakeDbSet<T>构造函数中添加以下内容作为最后一行,这样我就不需要在所有的测试中重复写了: Setup(m => m.Include(It.IsAny<string>())).Returns(Object); 目前,我使用这种方法成功地运行了许多使用Moq的单元测试。 - KarlZ
很棒的解决方案,尽管我能够使用我的原始设置,而且我没有创建一个虚假的类,但是这个解决方案非常有帮助。 - krystan honour

15
使用Moq框架,这个方法可以处理我投入它的一切。
    public static Mock<DbSet<T>> GetMockSet<T>(this ObservableCollection<T> list) where T : class
    {
        var queryable = list.AsQueryable();
        var mockList = new Mock<DbSet<T>>(MockBehavior.Loose);

        mockList.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
        mockList.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
        mockList.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
        mockList.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
        mockList.Setup(m => m.Include(It.IsAny<string>())).Returns(mockList.Object);
        mockList.Setup(m => m.Local).Returns(list);
        mockList.Setup(m => m.Add(It.IsAny<T>())).Returns((T a) => { list.Add(a); return a; });
        mockList.Setup(m => m.AddRange(It.IsAny<IEnumerable<T>>())).Returns((IEnumerable<T> a) => { foreach (var item in a.ToArray()) list.Add(item); return a; });
        mockList.Setup(m => m.Remove(It.IsAny<T>())).Returns((T a) => { list.Remove(a); return a; });
        mockList.Setup(m => m.RemoveRange(It.IsAny<IEnumerable<T>>())).Returns((IEnumerable<T> a) => { foreach (var item in a.ToArray()) list.Remove(item); return a; });

        return mockList;
    }

使用它只需执行以下操作:

    mockContext.Setup(p => p.<DbSetToMock>).Returns(<observableCollection to use as data>.GetMockSet().Object);`

如果上下文实现了接口,这将非常有效,因为您永远不必处理EF。

编辑:

额外位的原因是我们可以在测试中检查结果,如果添加或删除,我们可以检查通过的集合,它将在测试后具有结果。


5
这对我很有效-它避免了“NullReferenceException”。我缺少的是,我认为我需要模拟 Include (Expresssion<Func<T, TProperty>>) 重载 - 但你不能因为它是扩展方法 - 实际上你只需要模拟核心 Include(string) 方法就可以让它起作用。 - oatsoda
这对我不起作用。当我添加了这行代码 "mockList.Setup(m => m.Include(It.IsAny<string>())).Returns(mockList.Object);" 后,"Include" 出现了红色波浪线,并显示错误信息 "CS0411 方法 'EntityFrameworkQueryableExtensions.Include<TEntity, TProperty>(IQueryable<TEntity>, Expression<Func<TEntity, TProperty>>)' 的类型参数无法从使用中推断出。请尝试显式指定类型参数。" - Paul Gorbas
@PaulGorbas 你可能漏掉了一个include,我凭记忆说,它应该是System.Data.Entity。 - Pedro.The.Kid
由于我已经有了一些IQueryable形式的静态测试数据,所以我需要将其转换为ObservableCollection形式。var list = new ObservableCollection<PermissionList>(_permissionLists); _mockContext.Setup(m => m.PermissionLists).Returns(list.GetMockSet().Object); - RandomHandle
针对任何使用 *Async 方法(例如 IQueryable<T>.ToListAsync())的代码,您需要采用一些额外的样板文件:https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking#testing-with-async-queries:mockList.As>() .Setup(m => m.GetAsyncEnumerator()) .Returns(new TestDbAsyncEnumerator(queryable.GetEnumerator())); mockList.As>() .Setup(m => m.Provider) .Returns(new TestDbAsyncQueryProvider(queryable.Provider)); - JoeBrockhaus

14

所以,如果稍微费点力就可以实现这个功能!

以下是我设置的模拟上下文和集合,并成功调用了include。我认为秘密在于通过对Provider、Expression和GetEnumerator的调用进行存根处理,并在将DbSet属性设置为存根集合时不将上下文存根化返回它们。

在GitHub上提供了一个可运行的示例

    [Test]
    public void CanUseIncludeWithMocks()
    {
        var child = new Child();
        var parent = new Parent();
        parent.Children.Add(child);

        var parents = new List<Parent>
            {
                parent
            }.AsQueryable();

        var children = new List<Child>
            {
                child
            }.AsQueryable();

        var mockContext = MockRepository.GenerateStub<TestContext>();

        var mockParentSet = MockRepository.GenerateStub<IDbSet<Parent>>();
        var mockChildSet = MockRepository.GenerateStub<IDbSet<Child>>();

        mockParentSet.Stub(m => m.Provider).Return(parents.Provider);
        mockParentSet.Stub(m => m.Expression).Return(parents.Expression);
        mockParentSet.Stub(m => m.GetEnumerator()).Return(parents.GetEnumerator());

        mockChildSet.Stub(m => m.Provider).Return(children.Provider);
        mockChildSet.Stub(m => m.Expression).Return(children.Expression);
        mockChildSet.Stub(m => m.GetEnumerator()).Return(children.GetEnumerator());

        mockContext.Parents = mockParentSet;
        mockContext.Children = mockChildSet;

        mockContext.Parents.Should().HaveCount(1);
        mockContext.Children.Should().HaveCount(1);

        mockContext.Parents.First().Children.FirstOrDefault().Should().NotBeNull();

        var query = mockContext.Parents.Include(p=>p.Children).Select(pc => pc);

        query.Should().NotBeNull().And.HaveCount(1);
        query.First().Children.Should().NotBeEmpty().And.HaveCount(1);

    }

有人想加一个使用Moq的例子吗?我遇到了与此处描述的相同的问题https://entityframework.codeplex.com/discussions/461731 - GetFuzzy
@GetFuzzy 我链接的 Github 项目中有 Moq 测试。我现在在一个非常慢的酒店网络连接上,所以无法确认,但我相当确定它们可以运行。 - Paul D'Ambra
Paul,谢谢你的提示,我已经查看了Github项目,并且它们可以正常工作...然而,我正在尝试使用EF6和新的DbSet,而不是使用IDbSet,虽然你可能会期望它们是相似的或者一样的,但我并没有太多的运气。不过我会继续努力的,因为这似乎是可行的... - GetFuzzy
另外,我正在使用Async,并遵循此指南。这可能比其他任何原因都更导致它无法正常工作... http://msdn.microsoft.com/en-us/data/dn314429.aspx - GetFuzzy
你试过用IDbSet吗?为了提供正确的细节以获得帮助,最好提出一个新问题。如果你这样做了,请在这里评论一个链接 - 我很乐意帮忙看看。 - Paul D'Ambra
嗨,保罗,我已经在https://dev59.com/K-o6XIcBkEYKwwoYKRLd上添加了我的问题,希望这只是像我如何为模拟创建数据这样简单的问题... - GetFuzzy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接