2015-12-29 66 views
2

我試圖單元測試一些使用實體框架6但在添加實體後加載相關實體時出現問題的Web Api 2控制器。我使用起訂量來創建一個嘲弄的DbContext和DbSet,並增加使用Moq爲Web API加載相關實體使用Moq for Web Api 2控制器測試

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

,以繞過_db.Entry(foo).State = EntityState.Modified;問題上的認沽行動。

在這個簡化的例子中,Api Action是一個Post,我們需要找回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();

回答

0

的多個模擬友好的方式有關解決方案的總體思路可以在這裏看到

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

目前,您的公司ntroller與EF緊密耦合,因此我的建議是將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(); 
} 

此依賴關係現在可以注入到控制器中,也可以被模擬。

這是一個潛在的控制器

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

希望這有助於