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

如何使用FakeItEasy模拟ASP.NET Core控制器基类的受保护方法?

问题

我正尝试为ASP.NET Core应用的REST API控制器添加基于FakeItEasy的单元测试。需要测试的公共控制器方法会调用父类中实现的受保护授权方法Authorize,该方法依赖单元测试中无法获取的运行时数据。请问在不做大的应用设计改动的前提下,绕过单元测试中的授权调用、让基类的受保护方法始终执行成功的最佳方式是什么?

一种方案是将授权调用实现为独立接口,但这需要修改应用设计,目前我希望在不做重大改动的前提下让单元测试正常运行。

相关代码概要

基控制器类

[ApiController]
public abstract class MicroserviceController: Microsoft.AspNetCore.Mvc.ControllerBase
{
    protected readonly ICorrelationContextAccessor _correlation;
    protected readonly IConfiguration _config;
    protected readonly ILogger _logger;

    protected MicroserviceController
    (
        IConfiguration config,
        ILogger logger,
        ICorrelationContextAccessor correlation
    )
    {
        _config = config;
        _logger = logger;
        _correlation = correlation;
    }

    protected virtual void Authorize(string[] scopes, string[] roles)
    {
        // 依赖ControllerBase方法和属性的授权逻辑
    }

    // 其他方法
}

控制器类

[ApiController]
public class UsersController: MicroserviceController
{
    private IUserService _userService;

    public UsersController
    (
        IConfiguration config,
        ILogger<UsersController> logger, 
        ICorrelationContextAccessor correlation, 
        IUserService userService
    )
    : base(config, logger, correlation)
    {
        _userService = userService;
    }

    [HttpGet]
    public ActionResult<User> GetUser(string userId)
    {
        try
        {
            Authorize(new string[] { "user_read", "user_write", "user_delete" }, null);
        }
        catch (UnauthorizedException ex)
        {
            return Unauthorized(ProcessError(ex));
        }
        catch (Exception ex)
        {
            return BadRequest(ProcessError(ex));
        }

        User user;
        
        try
        {
            user = _userService.GetUserById(userId);
        }
        catch (UnauthorizedException ex)
        {
            return Unauthorized(ProcessError(ex));
        }
        catch (NotFoundException ex)
        {
            return NotFound(ProcessError(ex));
        }
        catch (Exception ex)
        {
            return BadRequest(ProcessError(ex));
        }
        
        return Ok(user);
    }
}

单元测试类

public class UsersControllerTests
{
    private IConfiguration _config;
    private ILogger<UsersController> _logger; 
    private ICorrelationContextAccessor _correlation; 
    private IUserService _userService;

    public UsersControllerTests()
    {
        _config = A.Fake<IConfiguration>();
        _logger = A.Fake<ILogger<UsersController>>();
        _correlation = A.Fake<ICorrelationContextAccessor>();
        _userService = A.Fake<IUserService>();
    }

    [Fact]
    public void UsersController_GetUser_ReturnOk()
    {
        // Arrange
        UsersController usersController = new UsersController(_config, _logger, _correlation, _userService);

        string userId = "123456789";

        User user = A.Fake<User>();
        A.CallTo(() => _userService.GetUserById(userId)).Returns(user);

        // 如何让usersController.Authorize(scopes, roles)调用不执行任何逻辑?

        // Act
        ActionResult<User> result = usersController.GetUser(userId);

        // Assert
        result.Should().NotBeNull();
    }
}

请问是否可以抑制UsersController的GetUser方法对Authorize的调用?还是说我应该开始重新设计应用?


解决方案

不需要重新设计应用,利用FakeItEasy可以直接拦截基类中标记为virtual的受保护方法,让它在测试中不执行原有逻辑也不抛出异常。具体实现如下:

修改后的测试代码

[Fact]
public void UsersController_GetUser_ReturnOk()
{
    // Arrange
    // 创建UsersController的Fake实例,传入构造参数
    var usersController = A.Fake<UsersController>(_config, _logger, _correlation, _userService);
    
    // 拦截所有对Authorize方法的调用,使其不执行任何逻辑
    A.CallTo(usersController)
      .Where(call => call.Method.Name == "Authorize")
      .DoesNothing();

    string userId = "123456789";
    User user = A.Fake<User>();
    A.CallTo(() => _userService.GetUserById(userId)).Returns(user);

    // 指定GetUser方法执行真实的控制器逻辑,而非Fake默认的空实现
    A.CallTo(() => usersController.GetUser(userId)).CallsBaseMethod();

    // Act
    ActionResult<User> result = usersController.GetUser(userId);

    // Assert
    result.Should().NotBeNull();
    result.Result.Should().BeOfType<OkObjectResult>();
    ((OkObjectResult)result.Result).Value.Should().Be(user);
}

核心逻辑说明

  • 因为Authorize是基类中标记为virtual的受保护方法,FakeItEasy能够识别并拦截它的调用。如果方法未标记为virtual,这种方式会失效,但你的代码刚好满足条件。
  • 使用CallsBaseMethod()确保GetUser方法执行真实的业务逻辑,避免被FakeItEasy替换为空实现。
  • 整个方案完全在测试层修改,不需要改动任何应用代码,符合你“不做大设计改动”的需求。

后续优化建议(可选)

如果之后希望代码结构更解耦,可以将授权逻辑抽象为独立的IAuthorizationService接口,注入到控制器中。测试时直接Fake这个接口即可,无需依赖虚方法拦截。但这属于代码重构,目前可以先用上述方案快速解决测试问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 17:04:54