23

Have been googling for a solution to the problem on how to mock the include method on dbset in EF6. The problem is well documented here :-

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.

Thanks in advance.

3 Answers3

25

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. The Github example in the other answer unfortunately did not work: Set.Include() always returns 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:

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

anyone have an explanation for this?

Clint
  • 1,159
  • 1
  • 10
  • 19
  • 9
    `set.Setup(s => s.Include(It.IsAny())).Returns(set.Object);` works for me. – Sam Apr 24 '14 at 02:23
  • 5
    I wasn't sure, but `s.Include(It.IsAny())` **DOES** work when using lambdas as well. Kudos! – krillgar Sep 24 '15 at 19:52
  • 2
    I also add the following as the final line in the `FakeDbSet` constructor so that I don't need to keep repeating that in all the tests: `Setup(m => m.Include(It.IsAny())).Returns(Object);` I currently have many unit tests with Moq running successfully with that approach. – KarlZ Oct 12 '15 at 19:52
  • great solution allthough I was able to use my original set and I hadn't created a fake class but otherwise very helpful – krystan honour Mar 15 '16 at 15:09
15

using Moq framework this method works with everything I throw at it.

    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.

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.

Pedro.The.Kid
  • 1,968
  • 1
  • 14
  • 18
  • 5
    This worked for me - it avoids the `NullReferenceException`. The thing I was missing was that I thought I needed to Mock the `Include(Expresssion>)` overload - which you can't because it is an extension method - when actually you just need to mock the core `Include(string)` method to make things work. – oatsoda May 26 '17 at 06:59
  • This does not work for me. When I add the line "mockList.Setup(m => m.Include(It.IsAny())).Returns(mockList.Object);", "Include" gets a red squiggly with error "CS0411 The type arguments for method 'EntityFrameworkQueryableExtensions.Include(IQueryable, Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly." – Paul Gorbas Jun 28 '17 at 20:16
  • @PaulGorbas You must be missing an include I'm talking from memory but I think it is System.Data.Entity. – Pedro.The.Kid Jul 24 '17 at 14:57
  • Since I've already had some static test data in IQueryable form, I needed to cast to ObservableCollection form. `var list = new ObservableCollection(_permissionLists); _mockContext.Setup(m => m.PermissionLists).Returns(list.GetMockSet().Object);` – RandomHandle Jul 31 '18 at 23:12
  • For code that consumes any of the `*Async` methods _(ie: `IQueryable.ToListAsync()`)_, you'll need to adopt some additional boilerplate: 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 Mar 14 '20 at 00:21
14

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.

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

    }
Paul D'Ambra
  • 7,629
  • 3
  • 51
  • 96
  • Anyone want to add an example with Moq? I'm having the same problem as described here https://entityframework.codeplex.com/discussions/461731 – GetFuzzy Dec 09 '13 at 18:36
  • @GetFuzzy the github project I link to has Moq tests in. I'm on a really slowly hotel internet connection right now so can't check out to confirm but I'm pretty sure they run. – Paul D'Ambra Dec 09 '13 at 21:28
  • Paul, thanks for the hint, I did check out the github project and yes, they work... However, I'm trying this with EF6 and the new DbSet, not using IDbSet, and though you'd expect it'd be similar, or the same, I'm not having much luck. I'm going to keep working on it though, seems it should be doable... – GetFuzzy Dec 09 '13 at 23:40
  • Also, I'm using Async, and following this as a guide. Which may be more the cause of this not working than anything else... http://msdn.microsoft.com/en-us/data/dn314429.aspx – GetFuzzy Dec 09 '13 at 23:50
  • Have you tried it with IDbSet? It's probably best to ask a new question in order to provide the right level of detail to get assistance. Feel free to comment with a link here if you do - I'll happily take a look. – Paul D'Ambra Dec 10 '13 at 07:35
  • Hi Paul, I've added my question at http://stackoverflow.com/questions/20509315/moqing-enity-framework-6-include-using-dbset hopefully its something simple like how I'm creating data for the mock... – GetFuzzy Dec 11 '13 at 02:18