我想提出这个问题的背景.如果你愿意,可以跳过.很长一段时间以来,我一直密切关注stackoverflow和其他地方关于代码测试的正在进行的辩论,因为它与EF有关.一个阵营说,由于Linq与Objects&Sql和实现之间的差异,直接针对数据库进行测试.另一个人说通过嘲弄测试.
另一个意见分歧是使用存储库或接受DbContext和DbSet已经提供工作单元和存储库模式的问题.在我使用EF的时候,我已经尝试过这些阵营提供的各种意见组合.无论我做了什么,EF证明很难测试.
我很高兴地发现EF团队使得DbSet在EF 6中更具可模仿性.他们还提供了有关如何模拟DbSet的文档,包括使用Moq的异步方法.在处理涉及Web Api的最新项目时,我意识到如果我可以模拟EF,我可以跳过编写存储库,因为编写它们的正常原因是使事情变得可测试.在阅读了一些博客文章后,灵感来了......
- 背景结束---
实际问题是,遵循EF团队提供的关于如何使用Moq DbSet的示例代码,如果在任何代码中使用.Include(),则抛出ArgumentNullException.
关于SO的其他相关帖子
这是我的DbContext界面:
public interface ITubingForcesDbContext { DbSetWells { get; set; } int SaveChanges(); Task SaveChangesAsync(); Task SaveChangesAsync(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 ICollectionGeometryItems { get { return _geometryItems ?? (_geometryItems = new Collection ()); } protected set { _geometryItems = value; } } private ICollection _geometryItems; public virtual ICollection SurveyPoints { get { return _surveyPoints ?? (_surveyPoints = new Collection ()); } protected set { _surveyPoints = value; } } private ICollection _surveyPoints; public virtual ICollection TemperaturePoints { get { return _temperaturePoints ?? (_temperaturePoints = new Collection ()); } protected set { _temperaturePoints = value; } } private ICollection _temperaturePoints; }
这是直接使用EF DbContext的控制器
[Route("{id}")] public async TaskGet(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); }
最后这是失败的测试......
测试设置---
[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{ well1, well2, well3, well4 }.AsQueryable(); var mockWells = CreateMockSet(wells); _mockContext = new Mock (); _mockContext.Setup(c => c.Wells).Returns(mockWells.Object); } private static Mock > CreateMockSet (IQueryable data) where T : class { var mockSet = new Mock >(); mockSet.As >() .Setup(m => m.GetAsyncEnumerator()) .Returns(new TestDbAsyncEnumerator (data.GetEnumerator())); mockSet.As >() .Setup(m => m.Provider) .Returns(new 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()). 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 ; Assert.IsNotNull(response); Assert.IsNotNull(response.Content.GeometryItems); Assert.IsNotNull(response.Content.SurveyPoints); Assert.IsNotNull(response.Content.TemperaturePoints); }
TestDbAsyncQueryProvider和TestDbAsyncEnumerator直接来自引用的EF团队文档.我已经尝试了几种不同的变体,我如何为模拟创建数据,没有任何运气.