使用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个核心问题:
- 未等待异步方法执行完成:
GetMerchant是异步方法,直接赋值给result拿到的是Task<ActionResult<Merchant>>对象,而非实际返回的结果。 - 错误的类型断言:断言
result是Task<ActionResult<Merchant>>没有意义,我们需要的是Task执行后的实际返回值。 - 无效的对象比较:直接用
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
相关产品推荐
相关产品推荐

