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

前端 未结 5 945
南笙
南笙 2020-12-07 17:47

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 rega

相关标签:
5条回答
  • 2020-12-07 18:08

    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;
    }
    
    0 讨论(0)
  • 2020-12-07 18:14

    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);
    
    0 讨论(0)
  • 2020-12-07 18:15

    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
    0 讨论(0)
  • 2020-12-07 18:18

    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)
    
    0 讨论(0)
  • 2020-12-07 18:25

    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.

    0 讨论(0)
提交回复
热议问题