c# - 使用 Web Api 2 Controller 测试的 Moq 在模拟 DbContext 中加载相关实体

标签 c# entity-framework unit-testing asp.net-web-api2

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

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

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

在这个简化示例中,Api 操作是一个帖子,我们需要在其中取回 2 个相关实体(Bar 和 Qux)。

[ResponseType(typeof (Foo))]
public async Task<IHttpActionResult> 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<Foo>;
 Assert.IsNotNull(result);
 Assert.IsNotNull(result.Qux);
 Assert.IsNotNull(result.Bar);
}

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

最佳答案

关于解决方案的总体思路可以在这里看到

Mocking Entity Framework when Unit Testing ASP.NET Web API 2: dependency injection

目前,您的 Controller 与 EF 的耦合过于紧密,因此我的建议是从 Controller 中抽象出 DbContext 和 DbSet 依赖项,以便模拟友好。

为了解决 _db.Entry(foo).Reference(x => x.Bar).Load() 这里是基于您在您的应用程序中使用的依赖操作的简化抽象发布

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

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

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

现在可以将此依赖项注入(inject)到 Controller 中,也可以对其进行模拟。

这是一个潜在 Controller 的简化版本

public class FooController : ApiController {
    IUnitOfWork unitOfWork;

    public FooController (IUnitOfWork uow) {
        this.unitOfWork = uow;
    }

    [ResponseType(typeof(Foo))]
    public async Task<IHttpActionResult> 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<IUnitOfWork>();
    mockUnitOfWork.Setup(x => x.SaveChangesAsync())
        .Returns(() => Task.FromResult(0))
        .Callback(() => {
            model.Id = 1;
            saved = true;
        });
    mockUnitOfWork
        .Setup(x => x.LoadRelatedEntity<Foo, Qux>(It.IsAny<Foo>(), It.IsAny<Expression<Func<Foo, Qux>>>()))
        .Callback(() => model.Qux = new Qux());
    mockUnitOfWork
        .Setup(x => x.LoadRelatedEntity<Foo, Bar>(It.IsAny<Foo>(), It.IsAny<Expression<Func<Foo, Bar>>>()))
        .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<Foo>;

    //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();
}

希望对你有帮助

关于c# - 使用 Web Api 2 Controller 测试的 Moq 在模拟 DbContext 中加载相关实体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34515925/

相关文章:

c# - Azure Functions 不允许同步操作 为什么?

sql - 将普通密码转换为 EF Asp.Net Identity PasswordHash

c# - EF在sql表中创建新条目时需要主键

c# - 为什么 Entity Framework 6 不支持 Discriminator 的显式过滤?

javascript - Jest 和 React 导入 SCSS 文件的语法错误

unit-testing - 如何在 Jenkins 中保持单元测试输出

c# - 是否可以从 .NET 测试暴露于 COM 的程序集?

c# - 比较和交换作为非原子操作

node.js - mocha 没有在测试目录中运行所有测试

c# - 在保持纵横比的同时用鼠标调整框的大小?