You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Xunit.net验证ASP.NET异步方法返回的模型

如何验证异步API Action返回的Merchant模型属性值?

我正在为API编写单元测试,需要验证Action方法返回的Merchant模型中的属性值是否符合预期。现有测试代码无法正确获取异步方法返回的模型属性(如MerchantEmail)进行断言,相关代码如下:

现有测试代码

[Fact]
public async Task MerchantController_GetMGetProductByMerchantId_Merchant()
{
    //Arrange
    var merchantId = 1000;
    var merchant = new Merchant
    {
        MerchantId = merchantId,
        MerchantEmail = "akhil@gmail.com",
        MerchantName="akhil",
        MerchantPassword="12345",
        ConfirmPassword="12345",
    };
    A.CallTo(() => _merchantRepository.GetMerchantByID(1000)).Returns(merchant);
    var MerchantController = new MerchantController(_merchantRepository);

    //Act
    var result = MerchantController.GetMerchant(merchantId);

    //Assert
    result.Should()
          .BeOfType<Task<ActionResult<Merchant>>>();
   
    Assert.True(merchant.Equals(result));
}

Controller的GetMerchant方法

[HttpGet("{id}")]
public async Task<ActionResult<Merchant>> GetMerchant(int id)
{
    try
    {
        return await _repository.GetMerchantByID(id);
    }
    catch
    {
        return NotFound();
    }
}

GetMerchantByID仓储方法

public async Task<Merchant> GetMerchantByID(int MerchantId)
{
    return await _context.Merchants.FindAsync(MerchantId);
}

Merchant模型

public class Merchant
{
    [Key]
    public int MerchantId { get; set; }

    [Required(ErrorMessage = "Field can't be empty")]
    [DataType(DataType.EmailAddress, ErrorMessage = "E-mail is not valid")]
    public string? MerchantEmail { get; set; }

    public string? MerchantName { get; set; }

    [DataType(DataType.PhoneNumber)]
    [Display(Name = "Phone Number")]
    public string? MerchantPhoneNumber { get; set; }
    
    [Display(Name = "Please enter password"), MaxLength(20)]
    public string? MerchantPassword { get; set; }
    
    [NotMapped]
    [Display(Name = "ConfirmPassword")]
    [Compare("MerchantPassword", ErrorMessage = "Passwords don not match")]
    public string? ConfirmPassword { get; set; }
}

问题分析与修改方案

当前测试代码存在3个核心问题:

  1. 未等待异步方法执行完成:GetMerchant是异步方法,直接赋值给result拿到的是Task<ActionResult<Merchant>>对象,而非实际返回的结果。
  2. 错误的类型断言:断言result是Task<ActionResult<Merchant>>没有意义,我们需要的是Task执行后的实际返回值。
  3. 无效的对象比较:直接用merchant.Equals(result)是拿Merchant对象和Task对象比较,永远不会相等。

修改后的测试代码

[Fact]
public async Task MerchantController_GetMerchant_ReturnsExpectedMerchant()
{
    // Arrange
    var merchantId = 1000;
    var expectedMerchant = new Merchant
    {
        MerchantId = merchantId,
        MerchantEmail = "akhil@gmail.com",
        MerchantName = "akhil",
        MerchantPassword = "12345",
        ConfirmPassword = "12345",
    };
    A.CallTo(() => _merchantRepository.GetMerchantByID(merchantId)).Returns(expectedMerchant);
    var controller = new MerchantController(_merchantRepository);

    // Act
    var actionResult = await controller.GetMerchant(merchantId);

    // Assert
    // 验证返回的是OkObjectResult类型(直接返回模型会自动包装为OkResult)
    var okResult = actionResult.Result.Should().BeOfType<OkObjectResult>().Subject;
    // 从OkResult中取出实际的Merchant对象
    var returnedMerchant = okResult.Value.Should().BeOfType<Merchant>().Subject;
    
    // 断言属性值符合预期
    returnedMerchant.MerchantId.Should().Be(expectedMerchant.MerchantId);
    returnedMerchant.MerchantEmail.Should().Be(expectedMerchant.MerchantEmail);
    returnedMerchant.MerchantName.Should().Be(expectedMerchant.MerchantName);
}

关键修改点说明

  • 等待异步方法:用await调用controller.GetMerchant(merchantId),拿到实际的ActionResult<Merchant>对象。
  • 解析ActionResult:通过actionResult.Result获取内部的结果对象,这里因为控制器直接返回模型,所以会被包装为OkObjectResult,从中取出Value就是实际的Merchant实例。
  • 精准断言属性:针对每个需要验证的属性(如MerchantEmail、MerchantId)单独断言,确保属性值符合预期。

额外优化建议

  • 测试方法名修改为更清晰的MerchantController_GetMerchant_ReturnsExpectedMerchant,明确测试意图。
  • 如果需要测试异常分支(比如仓储抛出异常时返回NotFound),可以单独写一个测试用例,模拟仓储抛出异常,断言返回的是NotFoundResult。

内容的提问来源于stack exchange,提问作者akhil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 07:40:33