如何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
相关产品推荐
相关产品推荐

