【问题标题】:Unit Test OData V4 PUT action with XUnit and MOQ使用 XUnit 和 MOQ 进行单元测试 OData V4 PUT 操作
【发布时间】:2015-12-11 20:52:37
【问题描述】:

我的目标是在 OData v4 控制器中对 PUT 操作进行单元测试。

我使用 Entity Framework 6 Context 的 MOQ 和 NBuilder 来构建测试数据。

我能够成功测试 Get 和 Get(Id),但是当我从 PUT 操作中检索 HTTPActionResult 时无法运行断言。

我可以看到 HTTPActionResult 在调试模式下返回带有 Entity 属性的 UpdatedODataResult 对象,但我看不到在设计时使用它的方法。

有谁知道如何从 Async 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);
}

【问题讨论】:

  • Updated 方法看起来如何?根据代码,您的断言似乎应该是:verify(x =&gt; x.MarkAsModified(beforeEntity)), verify(x =&gt; x.SaveChangesAsync())....
  • 啊,所以使用 MOQ 来确认调用了 EF 方法,而不是检查响应中的更新实体。谢谢你的建议!

标签: unit-testing xunit asp.net-web-api-odata


【解决方案1】:

感谢@Old Fox 的建议。这是我最终得到的结果: *注意:出于解释目的,我包含了在线模拟设置。我使用这种方法在生产代码中创建了我的工厂:http://www.rhyous.com/2015/04/10/how-to-mock-an-entity-framework-dbcontext-and-its-dbset-properties/#comment-121594

这篇文章还帮助我拼凑起起订量设置:How to moq Entity Framework SaveChangesAsync?

此外,我在 cmets 中引用的链接显示了如何正确设置 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);
}

【讨论】:

  • 太棒了,我很高兴看到我的旧答案仍然相关并且对社区中的其他人有所帮助...请将您的答案标记为已接受。
  • 在获得完整答案之前,我还有一些工作要做。我正在处理 DbUpdateConcurrencyException 的异常处理逻辑。事实证明,在我的 DBSet 上模拟 Find 方法比我预期的更具挑战性。
  • 我想热烈感谢您的回答:stackoverflow.com/questions/23460893/…。这给了我所需的缺失部分。我会在更新单元测试后立即更新并接受答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-23
  • 2021-10-20
  • 2021-02-12
  • 1970-01-01
  • 1970-01-01
  • 2021-04-12
  • 1970-01-01
相关资源
最近更新 更多