Moqing Enity框架6.使用DbSet <>包含()
我想提出这个问题的背景。 跳过,如果你喜欢。 相当长一段时间以来,我一直密切关注正在进行的有关testing代码的stackoverflow和其他地方的辩论,因为它涉及到EF。 一个阵营说,由于Linq到Objects&Sql和实现之间的差异,直接针对数据库进行testing。 另一个说嘲笑testing。
另一个意见分歧是使用存储库的问题,或接受DbContext和DbSet已经提供工作单元和存储库模式。 在我使用EF的时候,我已经尝试过这些阵营提供的每一个意见组合。 无论我做了什么,EFcertificate是难以testing。
我非常兴奋地发现EF团队使得DbSet在EF 6中更加可嘲弄。他们还提供了关于如何模拟DbSet的文档 ,包括使用Moq的asynchronous方法。 在参与我最新的涉及Web Api的项目时,我意识到,如果我可以模拟EF,那么我可以跳过编写知识库,因为写这些知识库的正常原因是为了使testing成为可能。 阅读了一些这样的博客文章后,灵感来了…
– 背景结束—
实际的问题是,遵循EF小组关于如何使用Moq DbSet的示例代码,如果在任何代码中使用.Include(),则会引发ArgumentNullException。
SO上的其他相关post
这是我的DbContext接口:
public interface ITubingForcesDbContext { DbSet<WellEntity> Wells { get; set; } int SaveChanges(); Task<int> SaveChangesAsync(); Task<int> 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 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; }
这里是直接使用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); }
最后这里是失败的testing…
testing设置—
[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直接来自引用的EF小组文档。 我已经尝试了几种不同的变体来创build模拟数据,没有任何运气。
对于任何人在这个问题上.Include("Foo")
问题,关心如何解决NSubstitute和Entity Framework 6+的.Include("Foo")
问题,我可以通过以下方式绕过我的Include
调用:
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);
这是一个使用Moq的完整例子。 您可以将整个示例粘贴到unit testing课程中。 感谢@ jbaum012和@Skuli的评论。 我也推荐来自微软的优秀教程 。
// 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; }
玩这个和在这里引用答案安装结果调用扩展方法它看起来像Moq不能嘲笑静态扩展方法
我试图添加:
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()));
Moq抱怨说:
System.NotSupportedException:expression式引用不属于模拟对象的方法:t => t.FirstAsync()
所以看来有三个select:
- 重构你的代码来进一步隔离dbcontext,所以你不必testing这种行为
- 从DbSet切换到IDbSet而不是模拟DbContext
- 允许你的testing创build一个SQL压缩数据库并用数据填充它来运行你的testing
EF团队提供的示例DbSet
就是这样一个例子。
如果你想嘲笑Include
(或FindAsync
) ,你必须自己动手。
- 我如何以编程方式为entity frameworkCode-First设置连接string?
- 决定之间的NHibernate与entity framework?
- 从entity framework中删除单个logging?
- 使用entity framework创builddynamic查询
- entity framework4.1 DbContext重写SaveChanges以审计属性更改
- entity framework无法加载指定的元数据资源
- c#与multithreading服务器中的entity framework一起工作
- entity framework/ Linq EXpression从string转换为int
- entity framework6在将存储过程添加到数据模型之后无法构build