使用 XUnit 和最小起订量的单元测试 OData V4 PUT 操作
Unit Test OData V4 PUT action with XUnit and MOQ
我的目标是在 OData v4 控制器中对 PUT 操作进行单元测试。
我正在使用 Entity Framework 6 上下文的最小起订量和 NBuilder 来构建测试数据。
我能够成功测试 Get 和 Get(Id),但是当我从 PUT 操作中检索到 HTTPActionResult 时,我无法运行断言。
我可以在调试模式下看到 HTTPActionResult 返回一个带有实体 属性 的 UpdatedODataResult 对象,但我没有看到在设计时使用它的方法。
有谁知道如何从异步 PUT 操作响应中提取返回的实体?
代码如下:
using Models;
using WebApp.DAL;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
namespace WebApp.Controllers.Api
{
public class OrgsController : ODataController
{
private IWebAppDbContext db = new WebAppDbContext();
public OrgsController()
{
}
public OrgsController(IWebAppDbContext Context)
{
db = Context;
}
public async Task<IHttpActionResult> Put([FromODataUri] long Key, Org Entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (Key != Entity.Id)
{
return BadRequest();
}
db.MarkAsModified(Entity);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EntityExists(Key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(Entity);
}
//...other actions omitted
}
}
这是我的单元测试代码
[Theory, InlineData(5)]
public async Task Api_Put_Updates_Properties(long Id)
{
//arrange
var mockedDbContext = MocksFactory.GetMockContext<WebAppDbContext>();
mockedDbContext.Object.Orgs.AddRange(MocksFactory.GetMockData<Org>(10));
OrgsController _sut = new OrgsController(mockedDbContext.Object);
Org beforeEntity = new Org
{
Id = Id,
Name = "Put Org",
TaxCountryCode = "PutUs",
TaxNumber = "PutUs01"
};
//act
IHttpActionResult actionResult = await _sut.Put(Id, beforeEntity);
//assert
Assert.NotNull(actionResult);
//Assert.NotNull(actionResult.Entity);
//Assert.Equal(Id, actionResult.Entity.Id);
//Assert.Equal("Put Org", actionResult.Entity.Name);
}
谢谢@老狐狸的建议。这是我最终得到的:
*注意:出于解释目的,我包括了在线模拟设置。我使用这种方法在生产代码中创建我的工厂:http://www.rhyous.com/2015/04/10/how-to-mock-an-entity-framework-dbcontext-and-its-dbset-properties/#comment-121594
这个 post 也帮助我拼凑起订量设置:
此外,我在评论中引用的 link 显示了如何正确设置 GetEnumerator 方法。 MSDN 页面上的说明不正确。
差别很小,但很重要(那个发现花了我一周的时间)。确保正确设置该部分,否则您的上下文将有一个空的数据库集(本例中为 Orgs)。
[Theory, InlineData(1), InlineData(3), InlineData(5)]
public async Task Api_Put_Valid_Entity_Calls_ContextMethods_And_Returns_UpdatedODataResult(long Key)
{
//arrange
var data = new List<Org>
{
new Org { Id = 1, Name = "Name1", TaxCountryCode = "T1", TaxNumber = "TaxNumber1", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null },
new Org { Id = 2, Name = "Name2", TaxCountryCode = "T2", TaxNumber = "TaxNumber2", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null },
new Org { Id = 3, Name = "Name3", TaxCountryCode = "T3", TaxNumber = "TaxNumber3", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null },
new Org { Id = 4, Name = "Name4", TaxCountryCode = "T4", TaxNumber = "TaxNumber4", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null },
new Org { Id = 5, Name = "Name5", TaxCountryCode = "T5", TaxNumber = "TaxNumber5", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null }
}.AsQueryable();
var mockSet = new Mock<DbSet<Org>>();
mockSet.As<IQueryable<Org>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Org>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Org>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Org>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());
mockSet.Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>(ids => data.FirstOrDefault(d => d.Id == (int)ids[0]));
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(m => m.Orgs).Returns(mockSet.Object);
mockContext.Setup(m => m.SaveChangesAsync()).Returns(() => Task.Run(() => { return 1; })).Verifiable();
mockContext.Setup(m => m.MarkAsModified(It.IsAny<Org>())).Verifiable();
Org entity = new Org
{
Id = Key,
Name = "Put Org",
TaxCountryCode = "Put" + Key.ToString(),
TaxNumber = "Put" + Key.ToString()
};
var sut = new OrgsController(mockContext.Object);
//act
var actionResult = await sut.Put(Key, entity) as UpdatedODataResult<Org>;
//assert
mockContext.Verify(m => m.SaveChangesAsync(), Times.Once());
mockContext.Verify(m => m.MarkAsModified(entity), Times.Once());
Assert.IsType<UpdatedODataResult<Org>>(actionResult);
Assert.Equal(entity.Name, actionResult.Entity.Name);
Assert.Equal(entity.TaxCountryCode, actionResult.Entity.TaxCountryCode);
Assert.Equal(entity.TaxNumber, actionResult.Entity.TaxNumber);
}
我的目标是在 OData v4 控制器中对 PUT 操作进行单元测试。
我正在使用 Entity Framework 6 上下文的最小起订量和 NBuilder 来构建测试数据。
我能够成功测试 Get 和 Get(Id),但是当我从 PUT 操作中检索到 HTTPActionResult 时,我无法运行断言。
我可以在调试模式下看到 HTTPActionResult 返回一个带有实体 属性 的 UpdatedODataResult 对象,但我没有看到在设计时使用它的方法。
有谁知道如何从异步 PUT 操作响应中提取返回的实体?
代码如下:
using Models;
using WebApp.DAL;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
namespace WebApp.Controllers.Api
{
public class OrgsController : ODataController
{
private IWebAppDbContext db = new WebAppDbContext();
public OrgsController()
{
}
public OrgsController(IWebAppDbContext Context)
{
db = Context;
}
public async Task<IHttpActionResult> Put([FromODataUri] long Key, Org Entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (Key != Entity.Id)
{
return BadRequest();
}
db.MarkAsModified(Entity);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EntityExists(Key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(Entity);
}
//...other actions omitted
}
}
这是我的单元测试代码
[Theory, InlineData(5)]
public async Task Api_Put_Updates_Properties(long Id)
{
//arrange
var mockedDbContext = MocksFactory.GetMockContext<WebAppDbContext>();
mockedDbContext.Object.Orgs.AddRange(MocksFactory.GetMockData<Org>(10));
OrgsController _sut = new OrgsController(mockedDbContext.Object);
Org beforeEntity = new Org
{
Id = Id,
Name = "Put Org",
TaxCountryCode = "PutUs",
TaxNumber = "PutUs01"
};
//act
IHttpActionResult actionResult = await _sut.Put(Id, beforeEntity);
//assert
Assert.NotNull(actionResult);
//Assert.NotNull(actionResult.Entity);
//Assert.Equal(Id, actionResult.Entity.Id);
//Assert.Equal("Put Org", actionResult.Entity.Name);
}
谢谢@老狐狸的建议。这是我最终得到的: *注意:出于解释目的,我包括了在线模拟设置。我使用这种方法在生产代码中创建我的工厂:http://www.rhyous.com/2015/04/10/how-to-mock-an-entity-framework-dbcontext-and-its-dbset-properties/#comment-121594
这个 post 也帮助我拼凑起订量设置:
此外,我在评论中引用的 link 显示了如何正确设置 GetEnumerator 方法。 MSDN 页面上的说明不正确。 差别很小,但很重要(那个发现花了我一周的时间)。确保正确设置该部分,否则您的上下文将有一个空的数据库集(本例中为 Orgs)。
[Theory, InlineData(1), InlineData(3), InlineData(5)]
public async Task Api_Put_Valid_Entity_Calls_ContextMethods_And_Returns_UpdatedODataResult(long Key)
{
//arrange
var data = new List<Org>
{
new Org { Id = 1, Name = "Name1", TaxCountryCode = "T1", TaxNumber = "TaxNumber1", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null },
new Org { Id = 2, Name = "Name2", TaxCountryCode = "T2", TaxNumber = "TaxNumber2", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null },
new Org { Id = 3, Name = "Name3", TaxCountryCode = "T3", TaxNumber = "TaxNumber3", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null },
new Org { Id = 4, Name = "Name4", TaxCountryCode = "T4", TaxNumber = "TaxNumber4", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null },
new Org { Id = 5, Name = "Name5", TaxCountryCode = "T5", TaxNumber = "TaxNumber5", CreatedBy = 1, CreatedOn = DateTime.Now, ModifiedBy = 1, ModifiedOn = DateTime.Now, Accounts = null, ChargeRequestsFromMe = null, ChargeRequestsToMe = null, PaymentRequestsFromMe = null, PaymentRequestsToMe = null, Submissions = null }
}.AsQueryable();
var mockSet = new Mock<DbSet<Org>>();
mockSet.As<IQueryable<Org>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Org>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Org>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Org>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());
mockSet.Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>(ids => data.FirstOrDefault(d => d.Id == (int)ids[0]));
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(m => m.Orgs).Returns(mockSet.Object);
mockContext.Setup(m => m.SaveChangesAsync()).Returns(() => Task.Run(() => { return 1; })).Verifiable();
mockContext.Setup(m => m.MarkAsModified(It.IsAny<Org>())).Verifiable();
Org entity = new Org
{
Id = Key,
Name = "Put Org",
TaxCountryCode = "Put" + Key.ToString(),
TaxNumber = "Put" + Key.ToString()
};
var sut = new OrgsController(mockContext.Object);
//act
var actionResult = await sut.Put(Key, entity) as UpdatedODataResult<Org>;
//assert
mockContext.Verify(m => m.SaveChangesAsync(), Times.Once());
mockContext.Verify(m => m.MarkAsModified(entity), Times.Once());
Assert.IsType<UpdatedODataResult<Org>>(actionResult);
Assert.Equal(entity.Name, actionResult.Entity.Name);
Assert.Equal(entity.TaxCountryCode, actionResult.Entity.TaxCountryCode);
Assert.Equal(entity.TaxNumber, actionResult.Entity.TaxNumber);
}