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

如何Mock类方法?验证DecodeToken的XUnit测试Mock写法是否正确

问题分析与解决方案

你的核心需求是Mock GetTokenDetails() 来测试 DecodeToken 方法,但当前的实现方式无法直接Mock这个方法——因为GetTokenDetails是控制器的非虚实例方法,Moq这类Mock框架仅能Mock接口、抽象方法或虚方法。

下面是两种可行的解决思路:


方案一:将Token解析逻辑抽为独立服务(推荐)

这是最符合依赖注入原则的做法,把Token解析逻辑从控制器中剥离,封装成可注入的服务:

1. 定义服务接口与实现

public interface ITokenService
{
    GetTokenDetailsDto GetTokenDetails(HttpContext httpContext);
}

public class TokenService : ITokenService
{
    public GetTokenDetailsDto GetTokenDetails(HttpContext httpContext)
    {
        var token = httpContext.Request.Headers["Authorization"].ToString();
        var tokenbearer = token.Split(' ');
        var handler = new JwtSecurityTokenHandler();
        var decodedtoken = handler.ReadJwtToken(tokenbearer[1]);
        string user = decodedtoken.Claims.FirstOrDefault(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")?.ToString();
        string role = decodedtoken.Claims.FirstOrDefault(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role")?.ToString();
        var usr = user.Split(":");
        var rol = role.Split(":");
        string userName = usr[2].Trim();
        string userRole = rol[2].Trim();
        
        return new GetTokenDetailsDto()
        {
            UserName = userName,
            Role = userRole,
        };
    }
}

2. 修改控制器,注入服务

private readonly IEmployeeService _employeeService;
private readonly ITokenService _tokenService;

// 构造函数注入两个服务
public YourController(IEmployeeService employeeService, ITokenService tokenService)
{
    _employeeService = employeeService;
    _tokenService = tokenService;
}

[HttpGet]
[Authorize(Roles = "Admin")]
[Route("DecodeToken")]
public IActionResult DecodeToken()
{
    if (ModelState.IsValid)
    {
        var tokenResult = _tokenService.GetTokenDetails(HttpContext);

        var result = _employeeService.ServiceDecodeToken(tokenResult.UserName, tokenResult.Role);

        if (result.Httpcode == 200)
        {
            return Ok(result);
        }
        else
        {
            return StatusCode(500, result);
        }
    }
    else
        return BadRequest();
}

3. XUnit测试代码(使用Moq)

public class YourControllerTests
{
    [Fact]
    public void DecodeToken_WhenValidToken_ReturnsOk()
    {
        // Arrange
        var mockEmployeeService = new Mock<IEmployeeService>();
        var mockTokenService = new Mock<ITokenService>();
        
        // Mock GetTokenDetails的返回值
        mockTokenService.Setup(s => s.GetTokenDetails(It.IsAny<HttpContext>()))
            .Returns(new GetTokenDetailsDto { UserName = "testUser", Role = "Admin" });
        
        // Mock ServiceDecodeToken的返回值
        mockEmployeeService.Setup(s => s.ServiceDecodeToken("testUser", "Admin"))
            .Returns(new YourResultDto { Httpcode = 200 });
        
        var controller = new YourController(mockEmployeeService.Object, mockTokenService.Object);
        controller.ModelState.IsValid = true;

        // Act
        var result = controller.DecodeToken() as OkObjectResult;

        // Assert
        Assert.NotNull(result);
        Assert.Equal(200, result.StatusCode);
        mockEmployeeService.Verify(s => s.ServiceDecodeToken("testUser", "Admin"), Times.Once);
    }
}

方案二:将GetTokenDetails改为虚方法(临时方案)

如果不想重构服务,可以把GetTokenDetails改成虚方法,然后Mock控制器的子类:

1. 修改控制器方法

public virtual GetTokenDetailsDto GetTokenDetails()
{
    // 原有逻辑不变
}

2. 测试代码

public class YourControllerTests
{
    [Fact]
    public void DecodeToken_WhenValidToken_ReturnsOk()
    {
        // Arrange
        var mockEmployeeService = new Mock<IEmployeeService>();
        // 创建控制器的Mock,指定CallBase = true来调用其他非Mock的方法
        var mockController = new Mock<YourController>(mockEmployeeService.Object) { CallBase = true };
        
        // Mock GetTokenDetails方法
        mockController.Setup(c => c.GetTokenDetails())
            .Returns(new GetTokenDetailsDto { UserName = "testUser", Role = "Admin" });
        
        mockEmployeeService.Setup(s => s.ServiceDecodeToken("testUser", "Admin"))
            .Returns(new YourResultDto { Httpcode = 200 });

        // Act
        var result = mockController.Object.DecodeToken() as OkObjectResult;

        // Assert
        Assert.NotNull(result);
        Assert.Equal(200, result.StatusCode);
    }
}

总结

推荐用方案一,因为它遵循单一职责原则,代码解耦更彻底,后续维护和扩展更方便;方案二只是临时 workaround,长期来看会让控制器逻辑变得臃肿。

内容的提问来源于stack exchange,提问作者Voggu venu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 08:47:20