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

FakeItEasy测试控制器:非Fake对象与非虚方法拦截失败问题

问题分析与解决方案

错误根源

  1. 第一个错误原因:A.CallTo仅能作用于FakeItEasy创建的Fake对象,你直接new出来的真实控制器实例不属于Fake对象,因此无法通过它配置方法返回值。
  2. 第二个错误原因:FakeItEasy仅支持拦截虚方法(virtual)、抽象方法(abstract)、接口方法,你的IsUserBlockedFromAccesingAnnouncer方法是非虚方法,即使创建了Fake控制器也无法拦截。

解决方案

方案1:重构抽离服务(推荐,符合SOLID原则)

将控制器内部方法的逻辑提取到独立服务中,通过依赖注入实现解耦,测试时直接Fake这个服务即可。

  1. 定义服务接口与实现
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方法的逻辑
    }
}
  1. 修改控制器注入服务
public class AnnouncementsController : Controller
{
    private readonly IAnnouncerAccessService _accessService;

    public AnnouncementsController(IAnnouncerAccessService accessService)
    {
        _accessService = accessService;
    }

    // 被测试方法中调用_accessService.IsUserBlockedFromAccesingAnnouncer("aaa")
}
  1. 编写测试代码
[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控制器。

  1. 修改控制器方法
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)
    {
        // 原有逻辑
    }

    // 被测试方法
}
  1. 修改测试代码
[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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 22:25:27