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

SpringBootTest测试显式关闭上下文后出现IllegalStateException报错

解决SpringBoot测试中关闭上下文后抛出的IllegalStateException问题

场景重现

我有一个显式关闭应用上下文的服务类:

@Service
public class Foo {

    private ConfigurableApplicationContext context;

    public Foo(ConfigurableApplicationContext applicationContext) {
        this.context = applicationContext;
    }

    public void closeContext() {
        Thread thread = new Thread(() -> {
            context.close();
            System.out.println("Context closed");
        });
        thread.setDaemon(false);
        thread.start();
    }
}

为了验证上下文是否被关闭,我编写了如下测试类:

@SpringBootTest(classes = X12Application.class)
class X12ApplicationTests {

    @Autowired
    private Foo foo;
    @Autowired
    private ConfigurableApplicationContext context;

    @Test
    void contextLoads() throws InterruptedException {
       assertFalse(context.isClosed());
       foo.closeContext();
       Thread.sleep(5000);
       assertTrue(context.isClosed());
    }
}

测试中的断言全部通过,但测试结束时抛出以下错误:

java.lang.IllegalStateException: The ApplicationContext loaded for [MergedContextConfiguration@c2cf597 testClass = com.example.x12.X12ApplicationTests, locations = [], classes = [com.example.x12.X12Application], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@79da8dc5, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@696da30b, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@693fe6c9, org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@306f16f3, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@14555e0a, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@a76cda49], contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] is not active. This may be due to one of the following reasons: 1) the context was closed programmatically by user code; 2) the context was closed during parallel test execution either according to @DirtiesContext semantics or due to automatic eviction from the ContextCache due to a maximum cache size policy.

    at org.springframework.util.Assert.state(Assert.java:101)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:132)
    at org.springframework.test.context.TestContext.publishEvent(TestContext.java:95)
    at org.springframework.test.context.event.EventPublishingTestExecutionListener.afterTestClass(EventPublishingTestExecutionListener.java:168)
    at org.springframework.test.context.TestContextManager.afterTestClass(TestContextManager.java:538)
    at org.springframework.test.context.junit.jupiter.SpringExtension.afterAll(SpringExtension.java:141)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

解决方案

方案1:添加@DirtiesContext注解

问题根源是测试框架在测试结束后尝试访问已经被手动关闭的上下文。通过添加@DirtiesContext注解,告诉Spring测试框架该上下文已经被修改(脏了),不需要再复用或访问它。

修改测试类,在类或方法级别添加注解:

@SpringBootTest(classes = X12Application.class)
@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
class X12ApplicationTests {
    // 测试代码不变
}

如果测试类里只有这一个测试方法,也可以用类级别的@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)。

方案2:使用Mock替代真实上下文(更推荐)

直接关闭真实的Spring上下文会干扰测试框架的生命周期管理,更好的做法是模拟ConfigurableApplicationContext,验证close()方法是否被调用,而不是真的关闭上下文。

修改测试类如下:

@SpringBootTest(classes = X12Application.class)
class X12ApplicationTests {

    @Autowired
    private Foo foo;
    @MockBean
    private ConfigurableApplicationContext context;

    @Test
    void contextLoads() throws InterruptedException {
       Mockito.verify(context, Mockito.never()).close();
       foo.closeContext();
       // 等待异步线程执行
       Thread.sleep(5000);
       Mockito.verify(context, Mockito.times(1)).close();
    }
}

该方案避免了修改真实上下文的状态,不会触发测试框架的报错,需要确保项目中引入了Mockito依赖。

方案3:自定义测试执行监听器(复杂场景)

如果以上两种方案不适用,可以自定义TestExecutionListener,在afterTestClass阶段跳过对已关闭上下文的访问。但这种方式需要编写额外代码,仅建议在特殊场景下使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:48:25