使用Moq进行模拟的DbContext中的相关实体的Web Api 2控制器测试 [英] Loading Related Entities in Mocked DbContext using Moq for Web Api 2 Controller Tests

查看:71
本文介绍了使用Moq进行模拟的DbContext中的相关实体的Web Api 2控制器测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试对某些使用Entity Framework 6的Web Api 2控制器进行单元测试,但是在添加实体后,相关实体的加载存在问题.我正在使用Moq创建模拟的DbContext和DbSet,并添加了

I'm trying to unit test some Web Api 2 Controllers that use Entity Framework 6 but having issues with the loading of the related entities after the entity has been added. I'm using Moq to create a mocked DbContext and DbSet, and have added

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

解决Put动作中的_db.Entry(foo).State = EntityState.Modified;问题.

to get around the _db.Entry(foo).State = EntityState.Modified; issue on a Put action.

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

The Api Action is a Post in this simplified example where we need to get back 2 related entities (Bar and 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);
}

然后将是一个简化的测试

And then a simplified test would be

[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();

Is there a more mock-friendly way of doing _db.Entry(foo).Reference(x => x.Bar).Load();

推荐答案

有关解决方案的一般想法,请参见此处

The general idea about the solution can be seen here

在单元测试ASP.NET Web API 2时模拟实体框架

当前,您的控制器与EF耦合太紧,所以我的建议是从控制器中抽象出DbContext和DbSet依赖项,以使其对模拟友好.

Currently, your controller is coupled too tightly to EF so my advice would be to abstract the DbContext and DbSet dependency out of the controller so that it can become mock-friendly.

要绕过_db.Entry(foo).Reference(x => x.Bar).Load(),这里是根据帖子中使用的内容对依赖动作的简化抽象

To get around _db.Entry(foo).Reference(x => x.Bar).Load() here is a simplified abstraction of the dependent actions based on what you are using in your post

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

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

and allow a concrete implementation to be able to do this.

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

现在可以将此依赖项注入到控制器中,也可以对其进行模拟.

This dependency can now be injected into the controller and can also be mocked.

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

Here is a simplified version of a potential 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);
    }
}

从那里开始只需创建测试即可.

From there it's just a matter of creating your test.

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

希望这会有所帮助

这篇关于使用Moq进行模拟的DbContext中的相关实体的Web Api 2控制器测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆