Linq Moqing实体框架6.Include()使用DbSet<&燃气轮机;

Linq Moqing实体框架6.Include()使用DbSet<&燃气轮机;,linq,entity-framework,unit-testing,Linq,Entity Framework,Unit Testing,我想介绍一下这个问题的背景。如果你愿意,可以跳过。在相当长的一段时间里,我一直在密切关注有关stackoverflow和其他关于与EF相关的代码测试的争论。一个阵营说,直接针对数据库进行测试,因为LINQtoObjects&Sql和实现之间存在差异。另一种说法是通过模仿进行测试 另一个意见分歧是使用存储库的问题,或者接受DbContext和DbSet已经提供了一个工作单元和存储库模式。在我使用EF的过程中,我尝试了这些阵营提供的各种观点。不管我做了什么,EF都很难测试 我很兴奋地发现EF团队在E

我想介绍一下这个问题的背景。如果你愿意,可以跳过。在相当长的一段时间里,我一直在密切关注有关stackoverflow和其他关于与EF相关的代码测试的争论。一个阵营说,直接针对数据库进行测试,因为LINQtoObjects&Sql和实现之间存在差异。另一种说法是通过模仿进行测试

另一个意见分歧是使用存储库的问题,或者接受DbContext和DbSet已经提供了一个工作单元和存储库模式。在我使用EF的过程中,我尝试了这些阵营提供的各种观点。不管我做了什么,EF都很难测试

我很兴奋地发现EF团队在EF 6中诞生。他们还提供了如何模拟DbSet,包括使用Moq的异步方法。在从事涉及Web Api的最新项目时,我意识到,如果我可以模拟EF,我就可以跳过编写存储库,因为编写存储库的正常原因是为了使事情可测试。灵感来自于阅读了一些博客文章,如

--背景结束---

实际的问题是,按照EF团队给出的关于如何Moq DbSet的示例代码,如果在任何代码中使用了.Include(),则会引发ArgumentNullException

其他

以下是我的DbContext接口:

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

    int SaveChanges();

    Task<int> SaveChangesAsync();

    Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}
公共界面ITubingForcesDbContext
{
DbSet Wells{get;set;}
int SaveChanges();
任务saveChangesSync();
任务保存更改同步(CancellationToken CancellationToken);
}
这是我的控制器处理的主要实体

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;
}
公共类实体
{
公共int Id{get;set;}
public DateTime DateUpdated{get;set;}
由{get;set;}更新的公共字符串
[必需]
公共字符串名称{get;set;}
公共字符串位置{get;set;}
公共虚拟公司公司{get;set;}
公共虚拟ICollection GeometryItems
{
获取{return{geometryItems???({geometryItems=new Collection());}
受保护集{u geometryItems=value;}
}
私人收集(几何测量);;
公共虚拟ICollection调查点
{
获取{return\u surveyPoints???(\u surveyPoints=new Collection());}
受保护集{u surveyPoints=value;}
}
私人ICollection调查点;
公共虚拟ICollection TemperaturePoints
{
获取{return\u temperaturePoints???(\u temperaturePoints=new Collection());}
受保护集{u temperaturePoints=value;}
}
私人ICollection_温度点;
}
下面是直接使用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);
}
[路由(“{id}”)]
公共异步任务Get(int-id)
{
var query=等待context.Wells。
包括(x=>x.GeometryItems)。
包括(x=>x.SurveyPoints)。
包括(x=>x.TemperaturePoints)。
SingleOrDefaultAsync(x=>x.Id==Id);
if(查询==null)
{
返回NotFound();
}
var model=ModelFactory.Create(查询);
返回Ok(型号);
}
最后是失败的测试

测试设置---

[分类初始化]
公共静态void ClassInitialize(TestContext testContest)
{
var well1=新的WellEntity{Name=“Well 1”};
var well2=新的WellEntity{Name=“Well 2”};
var well3=新的WellEntity{Name=“Well 3”};
var well4=新的WellEntity{Name=“Well 4”};
well1.GeometryItems.Add(new GeometryItem());
well1.TemperaturePoints.Add(新的TemperaturePoint());
well1.SurveyPoints.Add(newsurveypoint());
well2.GeometryItems.Add(new GeometryItem());
well2.TemperaturePoints.Add(新的TemperaturePoint());
well2.SurveyPoints.Add(newsurveypoint());
well3.GeometryItems.Add(new GeometryItem());
well3.TemperaturePoints.Add(新的TemperaturePoint());
well3.SurveyPoints.Add(newsurveypoint());
well4.GeometryItems.Add(new GeometryItem());
well4.TemperaturePoints.Add(新的TemperaturePoint());
well4.SurveyPoints.Add(newsurveypoint());
var wells=新列表{well1,well2,well3,well4}.AsQueryable();
var mockWells=CreateMockSet(wells);
_mockContext=newmock();
_Setup(c=>c.Wells).Returns(mockWells.Object);
}
私有静态Mock CreateMockSet(IQueryable数据),其中T:class
{
var mockSet=new Mock();
mockSet.As()
.Setup(m=>m.GetAsyncEnumerator())
.Returns(新的TestDbAsyncEnumerator(data.GetEnumerator());
mockSet.As()
.Setup(m=>m.Provider)
.Returns(新的TestDbAsyncQueryProvider(data.Provider));
mockSet.As().Setup(m=>m.Expression).Returns(data.Expression);
mockSet.As().Setup(m=>m.ElementType).Returns(data.ElementType);
mockSet.As().Setup(m=>m.GetEnumerator()。
返回(data.GetEnumerator());
返回模拟集;
}
[测试方法]
公共异步任务Get_ById_returnswelwithallchilddata()
{
//安排
var controller=newwellscontroller(_mockContext.Object);
//表演
var actionResult=await controller.Get(1);
//断言
var响应=作为OKNAGOTATEDCONTENTRESULT的操作结果;
Assert.IsNotNull(响应);
Assert.IsNotNull(response.Content.GeometryItems);
Assert.IsNotNull(response.Content.SurveyPoints);
Assert.IsNotNull(response.Content.TemperaturePoints);
}
TestDbAsyncQueryProvider和TestDbAsyncEnumerator直接来自引用的EF团队文档。对于如何创建dat,我尝试了几种不同的变体
   [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);
   }
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()));
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);
// 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;
}
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;
    }
        var mockCourseSet = SetupMockSetFor(entities => entities.Courses);
var foundCourses = dbContext.Courses.Include(c => c.CourseParticipants).Where(c => c.Id = courseId)