简体   繁体   English

实体框架6模拟包括dbset上的方法

[英]Entity framework 6 mocking include method on dbset

Have been googling for a solution to the problem on how to mock the include method on dbset in EF6. 一直在谷歌搜索有关如何在EF6中模拟dbset上的include方法的问题的解决方案。 The problem is well documented here :- 问题在这里有详细记载: -

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

Unfortunately though there does not seem to be a valid solution in there. 不幸的是,虽然似乎没有一个有效的解决方案。

Has anyone found a workaround to this? 有人找到了解决方法吗?

I do understand that we shouldn't really be mocking the EF6 context, but the project lead has insisted on it. 我确实理解我们不应该真的嘲笑EF6环境,但项目负责人坚持要求。

Thanks in advance. 提前致谢。

I had the same drama as @GetFuzzy above - it seemed that no matter what I did I couldn't avoid the NullReferenceException whenever an Include() call was made on a Moq DbSet. 我和上面的@GetFuzzy有同样的戏剧 - 似乎无论我做什么,只要在Moq DbSet上进行Include()调用,我就无法避免NullReferenceException。 The Github example in the other answer unfortunately did not work: Set.Include() always returns null. 遗憾的是,另一个答案中的Github示例不起作用:Set.Include()始终返回null。

After fiddling for a while I came up with a workaround for this. 在摆弄了一段时间后,我想出了一个解决方法。

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

I really don't like the clumsiness of having to have two fake DbSets but for some reason this doesn't work: 我真的不喜欢必须有两个假DbSets的笨拙但由于某种原因这不起作用:

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

anyone have an explanation for this? 有人对此有解释吗?

So, this is possible if a bit of a faff! 所以,如果有点冒险,这是可能的!

In the below I setup the mock context and sets and can call include successfully. 在下面我设置模拟上下文和设置,并可以成功调用包含。 I think that the secret sauce is in stubbing the calls through to Provider, Expression and GetEnumerator and in setting the DbSet properties on the stubbed context to the stubbed sets and not stubbing the context to returning them. 我认为秘诀就是将调用存根到Provider,Expression和GetEnumerator,并将存根上下文中的DbSet属性设置为存根集,而不是将上下文存根到返回它们。

A runnable example is available on GitHub 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);

    }

using Moq framework this method works with everything I throw at it. 使用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;
    }

to use it just do: 使用它只是做:

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

This works grate if the context implements a Interface as you never have to do anything with EF. 如果上下文实现了一个接口,那么这就好了,因为你永远不需要对EF做任何事情。

EDIT: 编辑:

The reason for the extra bits is we can check the result on the test, if we add or remove we can check the passed collection and it will have the result after the test. 额外位的原因是我们可以在测试中检查结果,如果我们添加或删除,我们可以检查传递的集合,它将在测试后得到结果。

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

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