55

I'd like to give the background to this question. Skip if you like. For quite a while I've paid close attention to the on going debates on stackoverflow and elsewhere regarding testing of code as it relates to EF. One camp says, test directly against a database because of the differences between the Linq to Objects & Sql and implementations. Another says test by mocking.

Another split in opinion is the issue of using repositories, or accepting that DbContext and DbSet already provide a unit of work and repository pattern. In the time that I've been using EF, I've tried about every combination of opinions provided by these camps. Regardless of what I've done, EF proves to be hard to test.

I was excited to find the EF team made DbSet more mockable in EF 6. They also provided documentation on how to mock DbSet, including async methods using Moq. In working on my latest project involving Web Api I realized that if I could mock EF, I could skip writing repositories, as the normal reason for writing them is to make things testable. Inspiration came after reading a few blog posts such as this...

--End of background ---

The actual problem is that following the example code given by the EF team on how to Moq DbSet, if .Include() is used in any code, an ArgumentNullException is thrown.

Other related post on SO

Here is my interface for DbContext:

public interface ITubingForcesDbContext
{
    DbSet<WellEntity> Wells { get; set; }

    int SaveChanges();

    Task<int> SaveChangesAsync();

    Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}

This is the main entity that my controller deals with

public class WellEntity
{
    public int Id { get; set; }
    public DateTime DateUpdated { get; set; }
    public String UpdatedBy { get; set; }

    [Required]
    public string Name { get; set; }
    public string Location { get; set; }

    public virtual Company Company { get; set; }

    public virtual ICollection<GeometryItem> GeometryItems
    {
        get { return _geometryItems ?? (_geometryItems = new Collection<GeometryItem>()); }
        protected set { _geometryItems = value; }
    }
    private ICollection<GeometryItem> _geometryItems;

    public virtual ICollection<SurveyPoint> SurveyPoints
    {
        get { return _surveyPoints ?? (_surveyPoints = new Collection<SurveyPoint>()); }
        protected set { _surveyPoints = value; }
    }
    private ICollection<SurveyPoint> _surveyPoints;

    public virtual ICollection<TemperaturePoint> TemperaturePoints
    {
        get { return _temperaturePoints ?? (_temperaturePoints = new Collection<TemperaturePoint>()); }
        protected set { _temperaturePoints = value; }
    }
    private ICollection<TemperaturePoint> _temperaturePoints;
}

Here is the controller which directly uses an EF DbContext

 [Route("{id}")]
 public async Task<IHttpActionResult> Get(int id)
 {
        var query = await TheContext.Wells.
                                   Include(x => x.GeometryItems).
                                   Include(x => x.SurveyPoints).
                                   Include(x => x.TemperaturePoints).
                                   SingleOrDefaultAsync(x => x.Id == id);
        if (query == null)
        {
            return NotFound();
        }
        var model = ModelFactory.Create(query);
        return Ok(model);
}

Finally here is the failing test...

Test Setup---

   [ClassInitialize]
   public static void ClassInitialize(TestContext testContest)
        {

            var well1 = new WellEntity { Name = "Well 1" };
            var well2 = new WellEntity { Name = "Well 2" };
            var well3 = new WellEntity { Name = "Well 3" };
            var well4 = new WellEntity { Name = "Well 4" };

            well1.GeometryItems.Add(new GeometryItem());
            well1.TemperaturePoints.Add(new TemperaturePoint());
            well1.SurveyPoints.Add(new SurveyPoint());

            well2.GeometryItems.Add(new GeometryItem());
            well2.TemperaturePoints.Add(new TemperaturePoint());
            well2.SurveyPoints.Add(new SurveyPoint());

            well3.GeometryItems.Add(new GeometryItem());
            well3.TemperaturePoints.Add(new TemperaturePoint());
            well3.SurveyPoints.Add(new SurveyPoint());

            well4.GeometryItems.Add(new GeometryItem());
            well4.TemperaturePoints.Add(new TemperaturePoint());
            well4.SurveyPoints.Add(new SurveyPoint());

            var wells = new List<WellEntity> { well1, well2, well3, well4 }.AsQueryable();

            var mockWells = CreateMockSet(wells);

            _mockContext = new Mock<ITubingForcesDbContext>();
            _mockContext.Setup(c => c.Wells).Returns(mockWells.Object);
   }

   private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> data) where T : class
    {
        var mockSet = new Mock<DbSet<T>>();

        mockSet.As<IDbAsyncEnumerable<T>>()
            .Setup(m => m.GetAsyncEnumerator())
            .Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator()));

        mockSet.As<IQueryable<T>>()
               .Setup(m => m.Provider)
               .Returns(new TestDbAsyncQueryProvider<T>(data.Provider));

        mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<T>>().Setup(m =>m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m=>m.GetEnumerator()).
        Returns(data.GetEnumerator());

        return mockSet;
   }

  [TestMethod]  
   public async Task Get_ById_ReturnsWellWithAllChildData()
    {
        // Arrange
        var controller = new WellsController(_mockContext.Object);

        // Act
        var actionResult = await controller.Get(1);

        // Assert
        var response = actionResult as OkNegotiatedContentResult<WellModel>;
        Assert.IsNotNull(response);
        Assert.IsNotNull(response.Content.GeometryItems);
        Assert.IsNotNull(response.Content.SurveyPoints);
        Assert.IsNotNull(response.Content.TemperaturePoints);
   }

TestDbAsyncQueryProvider & TestDbAsyncEnumerator come directly from the referenced EF team documentation. I've tried several different variations for how I create the data for the mock, haven't had any luck with it.

podiluska
  • 50,950
  • 7
  • 98
  • 104
GetFuzzy
  • 2,116
  • 3
  • 26
  • 42
  • 1
    I've just tried setting up async test (Added to Github project : https://github.com/pauldambra/includeTests) and they work fine using the same mocking as you have used. Are you able to push a failing example to Github or add more details on where your test is failing. This feels like your setup actually has a null where there shouldn't be... – Paul D'Ambra Dec 11 '13 at 14:06
  • 1
    Hi Paul, thanks for your help with this... To make your example represent my code, you have to change IDbSet to DbSet, and that will force you to change your CreateMockSet method to match what I have posted above. Then you will see the same behavior I'm seeing, which is System.ArgumentNullException on var queryTask = await mockContext.Object.Parents.Include(p => p.Children).FirstAsync(); – GetFuzzy Dec 11 '13 at 16:28
  • The methods on DbSet are not virtual which means when Moq mocks the class it can't override those methods. IDbSet exists (at least partly) to allow you to mock more easily. Why do you want to use DbSet and not IDbSet? – Paul D'Ambra Dec 12 '13 at 07:19
  • In the background above there is a link 'DbSet more mockable', in that article it describes says this.. "Even if you wanted to create your own fakes (or test doubles) in EF6, you can do that with DbSet now, not IDbSet. IDbSet is still there for backwards compatibility." With EF 6 they want you mocking DbSet, not IDbSet, and when I looked all the DbSet members are marked as virtual. – GetFuzzy Dec 12 '13 at 16:05
  • Here is someone else with the same problem. http://social.msdn.microsoft.com/Forums/en-US/ffc538b1-d9b2-4f76-a1fb-790adc3beda0/unit-testing-ef6-with-include?forum=adodotnetentityframework – GetFuzzy Dec 12 '13 at 21:10
  • Ha, so they are! They aren't listed as virtual on the front page for DbSet and I didn't dig in to the methods themselves... my bad – Paul D'Ambra Dec 13 '13 at 11:20
  • Yep, Include isn't Virtual... – Paul D'Ambra Dec 13 '13 at 11:47

5 Answers5

46

For anyone who stumbles upon this issue with interest on how to solve the .Include("Foo") problem with NSubstitute and Entity Framework 6+, I was able to bypass my Include calls in the following way:

var data = new List<Foo>()
{
    /* Stub data */
}.AsQueryable();

var mockSet = Substitute.For<DbSet<Foo>, IQueryable<Foo>>();
((IQueryable<Post>)mockSet).Provider.Returns(data.Provider);
((IQueryable<Post>)mockSet).Expression.Returns(data.Expression);
((IQueryable<Post>)mockSet).ElementType.Returns(data.ElementType);
((IQueryable<Post>)mockSet).GetEnumerator().Returns(data.GetEnumerator());

// The following line bypasses the Include call.
mockSet.Include(Arg.Any<string>()).Returns(mockSet);
roufamatic
  • 18,187
  • 7
  • 57
  • 86
John Lieb-Bauman
  • 1,426
  • 12
  • 25
  • 7
    You can use replace "Bar" with It.IsAny() to make it more general. Then you can call .Include(x=>x.Bar) because it calls .Include("Bar") internally – Skuli Sep 18 '14 at 13:39
  • 2
    This actually didnt work for me, but maybe I'm not following you exactly. Do you mean to use `.Include(Args.Any())` in substitute for `.Include("Bar")`, because this did not work for me :( – John Lieb-Bauman Sep 19 '14 at 13:43
  • 16
    I'm sorry, I'm using Moq. But yes you understand correctly. The line I use and that works for me is: `dbSet.Setup(m => m.Include(It.IsAny())).Returns(dbSet.Object);` where `dbSet = new Mock>();` Maybe the reason that I don't cast to IQueryable is relevant. – Skuli Sep 24 '14 at 11:23
  • 3
    Nope this does not work. While setting it up, it throws a System.NotSupportedException saying that Expression references a method that does not belong to the mocked object. Any ideas on how to fix this and get it working ? – Salman Hasrat Khan Apr 28 '15 at 12:47
  • 1
    Okay I got around that exception. The key to avoiding it is to not cast the dbset as an iQueryable as the official guides state. – Salman Hasrat Khan Apr 28 '15 at 12:53
  • 1
    Excuse me guys/gals, what is 'Post' in this context? Am I correct in assuming that it should be 'Foo'? – Ostmeistro Feb 10 '17 at 08:57
  • 1
    The test fails as it throws an error: Invalid setup on an extension method: x => x.Include(It.IsAny()) – Caloyski Jan 15 '18 at 11:01
39

Here is a complete example using Moq. You can paste the entire example into your unit test class. Thanks to comments by @jbaum012 and @Skuli. I also recommend the excellent tutorial from Microsoft.

// An Address entity
public class Address
{
    public int Id { get; set; }
    public string Line1 { get; set; }
}

// A Person referencing Address
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Address Address { get; set; }
}

// A DbContext with persons and devices
// Note use of virtual (see the tutorial reference)
public class PersonContext : DbContext
{
    public virtual DbSet<Person> Persons { get; set; }
    public virtual DbSet<Address> Addresses { get; set; }
}

// A simple class to test
// The dbcontext is injected into the controller
public class PersonsController
{
    private readonly PersonContext _personContext;

    public PersonsController(PersonContext personContext)
    {
        _personContext = personContext;
    }

    public IEnumerable<Person> GetPersons()
    {
        return _personContext.Persons.Include("Address").ToList();
    }
}

// Test the controller above
[TestMethod]
public void GetPersonsTest()
{
    var address = new Address { Id = 1, Line1 = "123 Main St." };
    var expectedPersons = new List<Person>
    {
        new Person { Id = 1, Address = address, Name = "John" },
        new Person { Id = 2, Address = address, Name = "John Jr." },
    };

    var mockPersonSet = GetMockDbSet(expectedPersons.AsQueryable());
    mockPersonSet.Setup(m => m.Include("Address")).Returns(mockPersonSet.Object);

    var mockPersonContext = new Mock<PersonContext>();
    mockPersonContext.Setup(o => o.Persons).Returns(mockPersonSet.Object);

    // test the controller GetPersons() method, which leverages Include()
    var controller = new PersonsController(mockPersonContext.Object);
    var actualPersons = controller.GetPersons();
    CollectionAssert.AreEqual(expectedPersons, actualPersons.ToList());
}

// a helper to make dbset queryable
private Mock<DbSet<T>> GetMockDbSet<T>(IQueryable<T> entities) where T : class
{
    var mockSet = new Mock<DbSet<T>>();
    mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(entities.Provider);
    mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(entities.Expression);
    mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(entities.ElementType);
    mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(entities.GetEnumerator());
    return mockSet;
}
Brett
  • 8,575
  • 5
  • 38
  • 51
  • 12
    The specific line that did the trick for me was mockPersonSet.Setup(m => m.Include("Address")).Returns(mockPersonSet.Object); – markdotnet Jun 23 '16 at 17:55
  • 4
    I tried this with EntityFrameworkCore 2.2.6 and get an error stating that I cannot mock the "Include" extension method for DbSet. – carlin.scott Nov 07 '19 at 20:04
8

Playing with this and referencing the answers here Setup result for call to extension method it looks like Moq cannot mock static extension methods

I tried to add:

mockSet.Setup(t => t.FirstAsync()).Returns(Task.FromResult(data.First()));
mockSet.Setup(t => t.FirstAsync(It.IsAny<Expression<Func<T, bool>>>())).Returns(Task.FromResult(data.First()));

And Moq complains that:

System.NotSupportedException : Expression references a method that does not belong to the mocked object: t => t.FirstAsync()

So it seems there are three options:

  1. refactor your code to further isolate dbcontext so you don't have to test this behaviour
  2. switch from DbSet to IDbSet instead of mocking DbContext
  3. allow your tests to create a SQL compact database and populate it with data in order to run your tests
TheVillageIdiot
  • 40,053
  • 20
  • 133
  • 188
Paul D'Ambra
  • 7,629
  • 3
  • 51
  • 96
  • Paul, thanks for your help, I had arrived at that conclusion last night, and I went back and looked at what I'm doing to see if it's really necessary to use .Includes() at all. I opted for lazy-loading, as I'm doing this for a web API controller, and when I generate my models to send over the wire, the navigation properties are accessed. That is to say, the navigation properties trigger the second query to the db, before the object heads out over the wire. So far this looks acceptable performance wise and allows me to continue with this easy path to testing... Wish EF was easier to test... – GetFuzzy Dec 13 '13 at 16:36
  • 4
    Hi Paul, one more note, someone from the EF team got back to me, it's not obvious but apparently these things can be mocked... Take a look at "Moq_DbSet_can_be_used_for_query_with_Include_extension_method_that_does_something" in MockableDbSetTests in the EF FunctionalTests project. Thanks, Arthur – GetFuzzy Dec 13 '13 at 19:01
  • 9
    For anyone coming late to this discussion. The solution provided here: http://entityframework.codeplex.com/SourceControl/latest#test/EntityFramework/FunctionalTests/TestDoubles/MockableDbSetTests.cs uses the `Include` overload which accepts a `string` not the lambda overload. – Adam Naylor Jan 28 '14 at 12:37
  • 1
    Strictly speaking, this answer is wrong now - the question asks about Enity Framework 6, and baum012's answer works for that (+ see Skuli's comment for Moq version) – Dunc Sep 25 '15 at 09:13
  • Well, it is two years old. :) – Paul D'Ambra Sep 25 '15 at 10:42
  • 1
    I'm trying to test a method that calls: `.Include(n => n.Something.Select( q => q.SomethingElse));` Any ideas? – philreed Feb 09 '16 at 11:43
3

I managed to mock Include in Moq with a generic approach. Albeit this doesn't cover all usages of Include(), only with string and Expression, but it suited my needs:

public Mock<DbSet<T>> SetupMockSetFor<T>(Expression<Func<DbContext, DbSet<T>>> selector) where T : class
    {
        var mock = new Mock<DbSet<T>>();

        mock.ResetCalls();

        this.EntitiesMock.Setup(m => m.Set<T>()).Returns(mock.Object);
        this.EntitiesMock.Setup(selector).Returns(mock.Object);

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

        try
        {
            mock.Setup(x => x.Include(It.IsAny<Expression<Func<T, object>>>()))
                .Returns(mock.Object);
        }
        catch
        {
            // Include only applies to some objects, ignore where it doesn't work
        }

        return mock;
    }

test usage:

        var mockCourseSet = SetupMockSetFor(entities => entities.Courses);

In service method:

var foundCourses = dbContext.Courses.Include(c => c.CourseParticipants).Where(c => c.Id = courseId)
Niklas Wulff
  • 3,497
  • 2
  • 22
  • 43
0

The example DbSet provided by the EF team is just that: an example.

If you want to mock Include (or FindAsync), you'll have to do it yourself.

Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 2
    I may have missed something (see https://github.com/pauldambra/includeTests) but you don't have to mock Include - admittedly it doesn't really do anything with an in-memory collection... – Paul D'Ambra Dec 11 '13 at 14:08