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

如何为含protected权限校验方法的Controller编写单元测试

解决Spring Controller单元测试中父类protected权限校验方法的空指针问题

原Controller代码

@RestController
@RequestMapping(value = ENDPOINT_URL)
public class MainController extends RestControllerBase {
    MainService mainService;

    public MainController(MainService mainService) {
        this.mainService = mainService;
    }

    @GetMapping
    public ResponseEntity<List<MainDataDto>> getDashboardData() {
        checkPermission();
        List<MainDataDto> result = mainService.getData();
        return ResponseEntity.ok().body(result);
    }
}

原测试代码

class MainControllerTest {
    MainController mainController;

    @Test
    public void test_getDashboardData_shouldReturn200Response() {
        MainService mainService = Mockito.mock(MainService.class);

        mainController = new MainController(mainService);

        List<MainDataDto> mockData = List.of(new MainDataDto());
        when(mainService.getData()).thenReturn(mockData);

        Assertions.assertEquals(HttpStatus.OK, mainController.getDashboardData().getStatusCode());
    }
}

抛出的异常

java.lang.NullPointerException: Cannot invoke "org.springframework.security.core.Authentication.getPrincipal()" because the return value of "org.springframework.security.core.context.SecurityContext.getAuthentication()" is null

方法1:模拟Spring Security上下文(推荐)

直接在测试中初始化SecurityContext,模拟合法的Authentication对象,让checkPermission方法能正常执行,更贴近真实运行场景:

class MainControllerTest {
    MainController mainController;

    @BeforeEach
    void setUp() {
        // 模拟Authentication和Principal对象
        Authentication auth = Mockito.mock(Authentication.class);
        // 若checkPermission需使用Principal的具体属性,可进一步mock对应逻辑
        when(auth.getPrincipal()).thenReturn("test-user");
        
        SecurityContext securityContext = Mockito.mock(SecurityContext.class);
        when(securityContext.getAuthentication()).thenReturn(auth);
        SecurityContextHolder.setContext(securityContext);
        
        // 初始化Controller
        MainService mainService = Mockito.mock(MainService.class);
        mainController = new MainController(mainService);
    }

    @AfterEach
    void tearDown() {
        // 测试结束后清空上下文,避免影响其他测试用例
        SecurityContextHolder.clearContext();
    }

    @Test
    public void test_getDashboardData_shouldReturn200Response() {
        List<MainDataDto> mockData = List.of(new MainDataDto());
        when(mainController.mainService.getData()).thenReturn(mockData);

        ResponseEntity<List<MainDataDto>> response = mainController.getDashboardData();
        Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
        Assertions.assertEquals(mockData, response.getBody());
    }
}

方法2:使用Mockito Spy跳过protected方法执行

若无需验证checkPermission的逻辑,仅需测试Controller核心业务流程,可通过Mockito Spy部分Mock Controller,跳过权限校验方法的实际执行:

class MainControllerTest {
    @Test
    public void test_getDashboardData_shouldReturn200Response() {
        MainService mainService = Mockito.mock(MainService.class);
        // 创建Controller的Spy对象,保留真实对象的部分逻辑
        MainController mainController = Mockito.spy(new MainController(mainService));
        
        // 跳过checkPermission方法的执行
        Mockito.doNothing().when(mainController).checkPermission();

        List<MainDataDto> mockData = List.of(new MainDataDto());
        when(mainService.getData()).thenReturn(mockData);

        ResponseEntity<List<MainDataDto>> response = mainController.getDashboardData();
        Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
    }
}

注意事项

  • 方法1适合需要验证权限校验逻辑的场景,确保checkPermission在测试环境下能正常工作;
  • 方法2适合快速跳过权限校验,聚焦Controller业务逻辑的测试场景;
  • 若checkPermission为final方法,Mockito Spy无法生效,此时只能使用方法1。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 02:22:36