Spring Boot集成测试中如何在Awaitility中使用认证上下文?
问题:Spring Boot集成测试中Awaitility无法访问认证上下文导致测试失败
在Spring Boot集成测试中使用Awaitility时,测试前已设置认证上下文,但由于Awaitility的断言运行在独立线程,无法访问该上下文,导致测试失败。简化后的代码如下:
@Test public void testMyServiceWithAuthentication() { // 登录用户 myService.performAction(); // 使用Awaitility等待条件满足 await().atMost(10, SECONDS).until(() -> { // 该断言运行在独立线程,无法访问主线程的认证上下文 return myService.checkCondition(); }); }
当Awaitility断言执行时,无法获取主线程设置的认证上下文,导致myService.checkCondition()因缺失认证而调用失败。需要解决如何让Awaitility断言访问认证上下文,或找到依赖认证的同线程等待替代方案。
解决方案
方案一:手动传递认证上下文到Awaitility线程
Spring Security的SecurityContextHolder默认通过ThreadLocal存储上下文,Awaitility的线程无法共享主线程的ThreadLocal数据。可以在主线程提前获取认证对象,在断言的lambda中手动设置到当前线程的上下文,执行完成后清理避免污染:
@Test public void testMyServiceWithAuthentication() { // 登录用户,获取当前认证上下文 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); myService.performAction(); await().atMost(10, SECONDS).until(() -> { // 在Awaitility线程中设置认证上下文 SecurityContextHolder.getContext().setAuthentication(auth); try { return myService.checkCondition(); } finally { // 执行完成后清理上下文,避免影响其他线程 SecurityContextHolder.clearContext(); } }); }
方案二:自定义Awaitility线程池统一注入上下文
如果多个测试用例都需要传递认证上下文,可以自定义Awaitility的线程池,在每个线程初始化时注入主线程的认证信息:
@BeforeEach void setupAwaitility() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); ExecutorService executor = Executors.newCachedThreadPool(runnable -> { Thread thread = new Thread(runnable); // 继承主线程的类加载器 thread.setContextClassLoader(Thread.currentThread().getContextClassLoader()); // 为新线程设置认证上下文 SecurityContextHolder.getContext().setAuthentication(auth); return thread; }); Awaitility.setExecutorService(executor); } @AfterEach void teardownAwaitility() { // 重置Awaitility配置,避免影响其他测试 Awaitility.reset(); }
方案三:同线程轮询替代Awaitility
如果不想使用多线程等待,可以自行实现同线程的轮询逻辑,这样自然能访问主线程的认证上下文:
@Test public void testMyServiceWithAuthentication() { // 登录用户 myService.performAction(); long timeoutMillis = 10000; long pollIntervalMillis = 500; long startTime = System.currentTimeMillis(); boolean conditionMet = false; while (System.currentTimeMillis() - startTime < timeoutMillis) { if (myService.checkCondition()) { conditionMet = true; break; } try { Thread.sleep(pollIntervalMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("等待被中断", e); } } assertTrue("超时未满足预期条件", conditionMet); }
内容的提问来源于stack exchange,提问作者user1552545
相关产品推荐
相关产品推荐

