使用Moq进行Web Api 2控制器测试,在Mocked DbContext中加载相关实体

我正在尝试对使用Entity Framework 6的一些Web Api 2控制器进行unit testing,但是在添加实体后加载相关实体时遇到问题。 我正在使用Moq创建一个模拟的DbContext和DbSet,并添加了

public virtual void MarkAsModified(T item) where T : class { Entry(item).State = EntityState.Modified; } 

绕过_db.Entry(foo).State = EntityState.Modified; 关于Put动作的问题。

在这个简化的例子中,Api Action是一个post,我们需要找回2个相关实体(Bar和Qux)。

 [ResponseType(typeof (Foo))] public async Task PostFoo(Foo foo) { if (!ModelState.IsValid) { return BadRequest(ModelState); } //Do other stuff _db.Foos.Add(foo); _db.Entry(foo).Reference(x => x.Bar).Load(); _db.Entry(foo).Reference(x => x.Qux).Load(); await _db.SaveChangesAsync(); return CreatedAtRoute("DefaultApi", new {id = foo.Id},foo); } 

然后进行简化测试

 [TestMethod] public async Task PostFoo() { var model = new Foo { Name="New Foo", QuxId = 99, Qux = null, BarId = 66, Bar = null }; var result = await _controller.PostFoo(model) as CreatedAtRouteNegotiatedContentResult; Assert.IsNotNull(result); Assert.IsNotNull(result.Qux); Assert.IsNotNull(result.Bar); } 

是否有更模拟友好的方式做_db.Entry(foo).Reference(x => x.Bar).Load();

关于解决方案的一般想法可以在这里看到

unit testing时的模拟entity frameworkASP.NET Web API 2:dependency injection

目前,您的控制器与EF的耦合太紧,因此我的建议是将DbContext和DbSet依赖项抽象出控制器,以便它可以变得模拟友好。

绕过_db.Entry(foo).Reference(x => x.Bar).Load()这里是基于你在post中使用的内容的依赖动作的简化抽象

 public interface IUnitOfWork { void Add(T item) where T : class; void MarkAsModified(T item) where T : class; void LoadRelatedEntity(T item, Expression> exp) where T : class where TRelated : class; Task SaveChangesAsync(); } 

并允许具体实现能够做到这一点。

 public void LoadRelatedEntity(T item, Expression> exp) where T : class where TRelated : class { _db.Entry(item).Reference(exp).Load(); } 

现在可以将此依赖项注入控制器,也可以进行模拟。

这是潜在控制器的简化版本

 public class FooController : ApiController { IUnitOfWork unitOfWork; public FooController (IUnitOfWork uow) { this.unitOfWork = uow; } [ResponseType(typeof(Foo))] public async Task PostFoo(Foo foo) { if (!ModelState.IsValid) { return BadRequest(ModelState); } //Do other stuff unitOfWork.Add(foo); await unitOfWork.SaveChangesAsync(); //Load related entities unitOfWork.LoadRelatedEntity(foo, x => x.Bar); unitOfWork.LoadRelatedEntity(foo, x => x.Qux); return CreatedAtRoute("DefaultApi", new { id = foo.Id }, foo); } } 

从那里开始,只需要创建测试。

 [TestMethod] public async Task TestPostFoo() { //Arrange bool saved = false; var model = new Foo { Name = "New Foo", QuxId = 99, Qux = null, BarId = 66, Bar = null }; var mockUnitOfWork = new Moq.Mock(); mockUnitOfWork.Setup(x => x.SaveChangesAsync()) .Returns(() => Task.FromResult(0)) .Callback(() => { model.Id = 1; saved = true; }); mockUnitOfWork .Setup(x => x.LoadRelatedEntity(It.IsAny(), It.IsAny>>())) .Callback(() => model.Qux = new Qux()); mockUnitOfWork .Setup(x => x.LoadRelatedEntity(It.IsAny(), It.IsAny>>())) .Callback(() => model.Bar = new Bar()); var controller = new TestsFooApiController(mockUnitOfWork.Object); controller.Request = new HttpRequestMessage { }; controller.Configuration = new HttpConfiguration(); //Act var result = await controller.PostFoo(model) as CreatedAtRouteNegotiatedContentResult; //Assert result.Should().NotBeNull(); result.Content.Should().NotBeNull(); result.Content.Id.Should().BeGreaterThan(0); result.Content.Qux.Should().NotBeNull(); result.Content.Bar.Should().NotBeNull(); saved.Should().BeTrue(); } 

希望这可以帮助