Spring 静态上下文访问器和集成测试

问题描述 投票:0回答:2

我们有一个 spring 组件,它将应用程序上下文设置为静态字段。然后可以从应用程序的其他部分访问该静态字段。我知道不应该使用

static
,但有时需要从非spring管理的bean访问spring上下文。例如。该字段看起来像这样:

public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    public ApplicationContext getApplicationContext() {
        return context;
    }

    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        context = ctx;
    }
}

(取自 http://www.dcalabresi.com/blog/java/spring-context-static-class/

问题是,在集成测试中使用 JUnit(或 Spock)框架时,会为具有

@TestPropertySource
@ContextConfiguration
等注释的测试创建一个新的 spring 上下文,在这种情况下,上下文将被缓存以供具有相同注释的其他测试使用。配置(Spring测试框架中的上下文缓存)。

但是,静态字段仅在创建 spring 上下文时更新。这意味着,当从缓存中检索测试上下文时,它当然不会更新静态字段,因为上下文在缓存之前已经初始化了。静态字段已被使用不同配置运行的先前测试创建的最后一个上下文覆盖,因此它看不到与启动测试的上下文相同的上下文。

结果是部分测试在一个 spring 上下文中运行,并且从它访问静态字段的那一刻起,它在另一个上下文中运行。

有人能解决这个问题吗?有人遇到同样的情况吗

spring spring-test
2个回答
0
投票

我也遇到过同样的问题。 可能的解决方案可能是在测试之前保存上下文并在测试之后恢复它。 为了方便起见,可以通过 junit 规则来完成:

public class ContextRestoreRule extends ExternalResource {

    private ApplicationContext context;

    @Override
    protected void before() throws Throwable {
        context = ApplicationContextProvider.getContext();
    }

    @Override
    protected void after() {
        ApplicationContextProvider.setContext(context);
    }
}

在测试中(修改上下文):

@ClassRule
public static ContextRestoreRule contextRestore = new ContextRestoreRule();

0
投票

TL;DR:您将需要一个自定义的

TestExecutionListener
,它是 Springs 插入测试生命周期的钩子。它提供了
beforeTestClass
/
afterTestClass
方法,接收当前
TestContext
作为参数

然后,您可以使用此测试上下文来更新您的
ApplicationContextProvider

详情

我在工作中两年多来一直在努力解决同样的问题。看似随机、奇怪的测试失败让我深深陷入了

SpringJUnit4Runner
,它的
DefaultApplicationContextCache
。 JUnit 的(故意伪随机)测试排序。 这个周末,我终于偶然发现了缺失的拼图:
TestExecutionListener.beforeTestClass(TestContext)

下面的示例侦听器实现会仔细检查

ApplicationContextProvider
的静态字段是否与测试的
ApplicationContext
(来自
TestContext
)匹配,如果不匹配则进行修复。
它在我们的数百个测试类的测试套件中触发了 9 次(正常的
ApplicationContextAware
行为正确处理了相当多的上下文切换。)

尽管如此,我仍然对我们的测试套件能够正常工作感到惊讶。 (话又说回来,我已经损失了好几个来寻找丢失的应用程序事件,结果才意识到生产代码是,再一次,在与测试监听不同的EventHandler实例上触发它们,ARGH) 使用您的自定义监听器

您需要通知您的测试实际

使用

监听器。 可用选项:

通过注释激活
    每个
  • 测试类
      @TestExecutionListeners(value = ApplicationContextProviderSyncer.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
    • 你需要 
    • MERGE_WITH_DEFAULTS
    • ,否则默认监听器 停止工作(这会破坏诸如
      @Autowired
      @Value
      之类的东西)
      boiler-platey,你可以忘记
    共享基类上的注释
  • 相同的注释,但在共享基类上
    • 如果你已经有底座的话很有用,如果没有的话就非常不方便
    默认通过 Springs“Factories Loader”机制激活
    • org.springframework.test.context.TestExecutionListener: ApplicationContextProviderSyncer.class
    •  中定义键 
      META-INF/spring.factories
      文档:
    • https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/tel-config.html
  • 示例
TestExecutionListener

实施
有关更详细的版本,请参阅

工作示例 GitLab 片段

import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.test.context.TestContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AbstractTestExecutionListener; /** * Keeps legacy-code adapter {@link ApplicationContextProvider} in sync with <em>currently active</em> Spring test context, correctly handling caching etc. * * @author Jules Kerssemakers */ public class ApplicationContextProviderSyncer extends AbstractTestExecutionListener { private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationContextProviderSyncer.class); @Override public void beforeTestClass(TestContext testContext) { ApplicationContext next = testContext.getApplicationContext(); ApplicationContext previous = null; if (ApplicationContextProvider.hasContext()) { previous = ApplicationContextProvider.getContext(); } if (!next.equals(previous)) { LOGGER.info(""" Synchronizing ApplicationContext for {}: replacing {} with {}""", testContext.getTestClass().getSimpleName(), previous, next); ApplicationContextProvider.setContext(next); } } }

© www.soinside.com 2019 - 2024. All rights reserved.