我们有一个 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 上下文中运行,并且从它访问静态字段的那一刻起,它在另一个上下文中运行。
有人能解决这个问题吗?有人遇到同样的情况吗
我也遇到过同样的问题。 可能的解决方案可能是在测试之前保存上下文并在测试之后恢复它。 为了方便起见,可以通过 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();
TL;DR:您将需要一个自定义的
TestExecutionListener
,它是 Springs 插入测试生命周期的钩子。它提供了 beforeTestClass
/afterTestClass
方法,接收当前 TestContext
作为参数。ApplicationContextProvider
。
我在工作中两年多来一直在努力解决同样的问题。看似随机、奇怪的测试失败让我深深陷入了
SpringJUnit4Runner
,它的DefaultApplicationContextCache
。 JUnit 的(故意伪随机)测试排序。
这个周末,我终于偶然发现了缺失的拼图:TestExecutionListener.beforeTestClass(TestContext)
。
下面的示例侦听器实现会仔细检查
ApplicationContextProvider
的静态字段是否与测试的 ApplicationContext
(来自 TestContext
)匹配,如果不匹配则进行修复。ApplicationContextAware
行为正确处理了相当多的上下文切换。)
尽管如此,我仍然对我们的测试套件能够正常工作感到惊讶。 (话又说回来,我已经损失了好几个周来寻找丢失的应用程序事件,结果才意识到生产代码是,再一次,在与测试监听不同的EventHandler实例上触发它们,ARGH) 使用您的自定义监听器
监听器。
可用选项:
@TestExecutionListeners(value = ApplicationContextProviderSyncer.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
MERGE_WITH_DEFAULTS
@Autowired
和@Value
之类的东西)boiler-platey,你可以忘记TestExecutionListener
。
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);
}
}
}