Loading Related Entities in Mocked DbContext using Moq for Web Api 2 Controller Tests

隐身守侯 提交于 2019-12-02 00:42:19

问题


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

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

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

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


回答1:


The general idea about the solution can be seen here

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

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.

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

Hope this helps



来源:https://stackoverflow.com/questions/34515925/loading-related-entities-in-mocked-dbcontext-using-moq-for-web-api-2-controller

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!