[spring-projects/spring-boot]当初始化期间抛出异常时,应用程序上下文在测试期间初始化了两次

2024-05-09 278 views
3

我注意到,当我进行 Spring Boot 测试时,上下文无法初始化,横幅会打印两次,我决定研究一下原因。

// spring-boot-test-autoconfigure 2.3.7.RELEASE
public class SpringBootDependencyInjectionTestExecutionListener extends DependencyInjectionTestExecutionListener {

    @Override
    public void prepareTestInstance(TestContext testContext) throws Exception {
        try {
            super.prepareTestInstance(testContext);
        }
        catch (Exception ex) {
            outputConditionEvaluationReport(testContext);
            throw ex;
        }
    }
.....
}

当初始化期间抛出异常时,outputConditionEvaluationReport(testContext)会调用 ,最终导致上下文在 内部被第二次初始化DefaultCacheAwareContextLoaderDelegate::loadContext

这比错误更令人讨厌,因为它只发生在测试代码中;成本只是开发人员的时间和构建资源。

在这种特殊情况下,我想快速失败,所以我抛出一个异常,因为我知道稍后会发生不好的事情。我试图防止经验不足的开发人员使用预期生命周期之外的资源。在测试过程中,我们会在早期生命周期阶段自动创建外部资源,这些资源只能在后期阶段以及应用程序启动后访问。如果开发人员违反此规则,并尝试在 bean 构造期间或早期生命周期中访问资源,我可以检测到它,并抛出 IlligealStateException。如果我不这样做,我稍后会从资源 Api 收到异常,并且如果初级开发人员从 HttpClient 收到 404/500 错误,他们可能很难确定他们违反了资源生命周期。

一种可能的解决方案是拥有一个AbortTestContextInitializationException, 如果您认为TestContext继续执行没有意义,并且尝试生成ConditionEvaluationReport.理想情况下,初始化的失败将被缓存,因此使用相同上下文的其他测试将立即失败,而不是使用构建资源,尝试为每个测试创建两次上下文。

回答

6

@QwertGold 我想我理解了描述,但是如果您可以提供一个显示问题的示例应用程序,那将会很有用。

5

酷,让我构建一个小项目来说明

4

我无法在一个简单的独立项目中重现这一点。当我调试它时,我可以看到创建 ApplicationContext 时的调用堆栈深度存在一些差异,因此我需要一些时间来找出为什么我的较大项目的行为不同,也许我会在此过程中学到一些东西 -我通常这样做;)

2

我弄清楚如何重现这个问题,当测试有真实的 Web 服务器环境时似乎会发生这种情况@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

我从 JUnit 4 和 boot 2.3.7 开始,因为这是我们使用的,但 Junit5 和 boot 2.4.2 的行为是相同的,存储库已提交这两种情况,https://github.com/QwertGold/spring -24888

7

遇到同样的问题,有更新吗?

我建议在 catch 块中添加以下内容{}

catch (Exception ex) {
     if(!(ex.getCause() instanceof UnsatisfiedDependencyException)){
       outputConditionEvaluationReport(testContext);
     }
     throw ex;
}

或者

catch (Exception ex) {
     if(testContext.hasApplicationContext()){
       outputConditionEvaluationReport(testContext);
     }
     throw ex;
}
4

感谢您提供的示例以及您在我们抽出时间开始研究这个问题时的耐心等待,@QwertGold。

模拟 Web 环境 (plain ) 不会出现此问题,@SpringBootTest因为上下文刷新是较早触发的(由ServletTestExecutionListener),我们不会替换它,因此不会尝试输出条件评估报告。

当有成熟的 Web 环境时,ServletTestExecutionListener不会运行,因为org.springframework.test.context.web.ServletTestExecutionListener.activateListener测试上下文中的属性已设置为false。这允许测试实例的准备工作继续进行,并SpringBootDependencyInjectionTestExecutionListener在刷新失败后尝试输出条件评估报告时触发双重初始化。

1

首次引入条件评估报告打印时,行为与 1.4.0-M2 中相同(请参阅https://github.com/spring-projects/spring-boot/issues/4901https://github.com/spring-boot/issues/4901)。 com/spring-projects/spring-boot/commit/e5f224118b7683faa14cc32b18cd3cbdae7664d7)以及在 1.4.1 中进行完善时(参见https://github.com/spring-projects/spring-boot/issues/6874https://github .com/spring-projects/spring-boot/commit/7134586310a378557113e08090b18bcfc399dd0a)。

当刷新失败时,上下文将不可用,因此我看不到当前方法如何访问上下文来生成报告。我们可以停止使用,SpringBootDependencyInjectionTestExecutionListener因为它似乎没有按预期工作,或者我认为我们需要以某种方式重新设计该方法,这意味着我们不需要从测试上下文中获取应用程序上下文来触发报告的生成。

标记即将召开的团队会议,以便我们可以讨论该做什么。

7

真是麻烦啊,花了一个小时调试这个...

2

只是揪着我的头发试图找出为什么我的上下文加载了两次,因为我试图找出是什么导致它在初始化过程中出现错误......不好玩!

8

我们可以停止使用,SpringBootDependencyInjectionTestExecutionListener因为它似乎没有按预期工作,或者我认为我们需要以某种方式重新设计该方法,这意味着我们不需要从测试上下文中获取应用程序上下文来触发报告的生成。

我赞成摆脱它SpringBootDependencyInjectionTestExecutionListener并用专用机制代替它。

您如何看待在 TestContext 框架中引入 SPI(在 Spring Framework 6.0 中)来“处理”ApplicationContext加载失败——基本上是 Boot 可以实现和注册的新接口(可能通过该spring.factories机制)?

相关问题:

5

听起来不错,萨姆。谢谢。我们将在 2.x 中保留本期所描述的问题,但在 3.0 中会有更好的前进道路。