使用FakeItEasy在XUnit中伪造异步方法时返回类型不匹配问题
问题场景
在ASP.NET项目中使用XUnit结合FakeItEasy测试异步控制器方法时,遇到返回类型不匹配的异常,异常信息如下:
Xunit.Sdk.XunitException: 'Expected type to be System.Threading.Tasks.Task
1[[Microsoft.AspNetCore.Mvc.ActionResult1[[System.Collections.Generic.List`1[[Amazon.Models.Merchant, AmazonAPI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Microsoft.AspNetCore.Mvc.Core, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]], but found System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[Microsoft.AspNetCore.Mvc.ActionResult`1[[System.Collections.Generic.List`1[[Amazon.Models.Merchant, AmazonAPI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Microsoft.AspNetCore.Mvc.Core, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Runtime.CompilerServices.IAsyncStateMachine, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].'
相关代码如下:
控制器方法
[HttpGet] public async Task<ActionResult<List<Merchant>>> GetMerchants() { return await _repository.GetMerchant(); }
仓储实现
public async Task<List<Merchant>> GetMerchant() { try { return await _context.Merchants.ToListAsync(); } catch { throw new NotImplementedException(); } }
修正后的测试代码
using Amazon.Controllers; using Amazon.Models; using Amazon.Repository; using FakeItEasy; using FluentAssertions; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace AmazonAPITest.Amazon_Controller_Merchant { public class Merchant_Controller_Test { private readonly IMerchantRepository _merchantRepository; public Merchant_Controller_Test() { _merchantRepository = A.Fake<IMerchantRepository>(); } [Fact] public async Task MerchantController_GetMerchants_ReturnsActionResultWithMerchantList() { // Arrange var fakeMerchantList = A.Fake<List<Merchant>>(); A.CallTo(() => _merchantRepository.GetMerchant()).Returns(Task.FromResult(fakeMerchantList)); var merchantController = new MerchantController(_merchantRepository); // Act var result = await merchantController.GetMerchants(); // Assert result.Should().NotBeNull(); result.Should().BeAssignableTo<ActionResult<List<Merchant>>>(); } } }
核心问题与解决要点
测试方法必须异步化
原测试方法为void类型,调用异步控制器方法时无法等待执行完成,且编译器会生成特殊的Task子类。需将测试方法改为async Task类型,并用await等待控制器方法执行完毕。禁止Fake Task类型
A.Fake<Task<T>>是错误用法,Task是编译器处理的特殊类型,不应被Fake。应使用Task.FromResult()创建已完成的Task实例,返回给Fake仓储。断言使用类型兼容性判断
BeOfType要求严格匹配类型,但异步方法返回的Task是编译器生成的AsyncStateMachineBox子类(继承自目标Task类型),因此改用BeAssignableTo来判断类型兼容性。
内容的提问来源于stack exchange,提问作者akhil

