Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moqing Entity Framework 6 .Include() using DbSet<>

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.

like image 247
GetFuzzy Avatar asked Dec 11 '13 02:12

GetFuzzy


People also ask

What is the use of DbSet in Entity Framework?

A DbSet represents the collection of all entities in the context, or that can be queried from the database, of a given type. DbSet objects are created from a DbContext using the DbContext.

What is DbSet in Entity Framework Core?

In Entity Framework Core, the DbSet represents the set of entities. In a database, a group of similar entities is called an Entity Set. The DbSet enables the user to perform various operations like add, remove, update, etc. on the entity set.

What is the difference between DbSet and DbContext?

Intuitively, a DbContext corresponds to your database (or a collection of tables and views in your database) whereas a DbSet corresponds to a table or view in your database.

Is DbSet part of DbContext?

The DbSet class represents an entity set that can be used for create, read, update, and delete operations. The context class (derived from DbContext ) must include the DbSet type properties for the entities which map to database tables and views.


1 Answers

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); 
like image 75
John Lieb-Bauman Avatar answered Sep 17 '22 11:09

John Lieb-Bauman