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

前端 未结 1 599
孤独总比滥情好
孤独总比滥情好 2021-01-21 02:02

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

相关标签:
1条回答
  • 2021-01-21 02:22

    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

    0 讨论(0)
提交回复
热议问题