FakeItEasy测试控制器:非Fake对象与非虚方法拦截失败问题
问题分析与解决方案
错误根源
- 第一个错误原因:
A.CallTo仅能作用于FakeItEasy创建的Fake对象,你直接new出来的真实控制器实例不属于Fake对象,因此无法通过它配置方法返回值。 - 第二个错误原因:FakeItEasy仅支持拦截虚方法(virtual)、抽象方法(abstract)、接口方法,你的
IsUserBlockedFromAccesingAnnouncer方法是非虚方法,即使创建了Fake控制器也无法拦截。
解决方案
方案1:重构抽离服务(推荐,符合SOLID原则)
将控制器内部方法的逻辑提取到独立服务中,通过依赖注入实现解耦,测试时直接Fake这个服务即可。
- 定义服务接口与实现
public interface IAnnouncerAccessService { Task<bool> IsUserBlockedFromAccesingAnnouncer(string cuid); } public class AnnouncerAccessService : IAnnouncerAccessService { private readonly OrganizerDbContext _context; private readonly UserManager<AppUser> _userManager; public AnnouncerAccessService(OrganizerDbContext context, UserManager<AppUser> userManager) { _context = context; _userManager = userManager; } public async Task<bool> IsUserBlockedFromAccesingAnnouncer(string cuid) { // 原IsUserBlockedFromAccesingAnnouncer方法的逻辑 } }
- 修改控制器注入服务
public class AnnouncementsController : Controller { private readonly IAnnouncerAccessService _accessService; public AnnouncementsController(IAnnouncerAccessService accessService) { _accessService = accessService; } // 被测试方法中调用_accessService.IsUserBlockedFromAccesingAnnouncer("aaa") }
- 编写测试代码
[Fact] public async Task AnnouncerIndex_Test_1() { // Arrange var fakeAccessService = A.Fake<IAnnouncerAccessService>(); A.CallTo(() => fakeAccessService.IsUserBlockedFromAccesingAnnouncer("aaa")) .Returns(Task.FromResult(false)); var controller = new AnnouncementsController(fakeAccessService); // Act // 调用控制器的被测试方法 // Assert // 验证测试结果 }
方案2:将内部方法改为虚方法(快速临时解决)
如果不想重构代码,可将目标方法标记为virtual,再创建带构造参数的Fake控制器。
- 修改控制器方法
public class AnnouncementsController : Controller { private readonly OrganizerDbContext _context; private readonly UserManager<AppUser> _userManager; public AnnouncementsController(OrganizerDbContext context, UserManager<AppUser> userManager) { _context = context; _userManager = userManager; } // 标记为virtual public virtual async Task<bool> IsUserBlockedFromAccesingAnnouncer(string cuid) { // 原有逻辑 } // 被测试方法 }
- 修改测试代码
[Fact] public async Task AnnouncerIndex_Test_1() { // Arrange var fakeContext = A.Fake<OrganizerDbContext>(); var fakeUserManager = A.Fake<UserManager<AppUser>>(); // 创建带构造参数的Fake控制器 var fakeController = A.Fake<AnnouncementsController>(x => x.WithArgumentsForConstructor(() => new AnnouncementsController(fakeContext, fakeUserManager))); // 配置虚方法返回值 A.CallTo(() => fakeController.IsUserBlockedFromAccesingAnnouncer("aaa")) .Returns(Task.FromResult(false)); // Act // 调用fakeController的被测试方法 // Assert // 验证测试结果 }
注意:此方法会破坏控制器封装性,仅适合临时快速解决,长期维护不推荐。
内容的提问来源于stack exchange,提问作者Samo
相关产品推荐
相关产品推荐

