JUnit 5:将 spring 组件注入 Extension (BeforeAllCallback / AfterAllCallback)
Posted
技术标签:
【中文标题】JUnit 5:将 spring 组件注入 Extension (BeforeAllCallback / AfterAllCallback)【英文标题】:JUnit 5: Inject spring components to Extension (BeforeAllCallback / AfterAllCallback) 【发布时间】:2019-11-16 04:19:30 【问题描述】:tl;dr:如何在所有测试运行之前将自定义数据提供程序实例化为 Spring 组件?
是否有一种聪明的方法可以将 Spring 组件注入到实现 BeforeAllCallback
的自定义 JUnit Jupiter 扩展中? beforeAll
方法应该在使用@ExtendWith(OncePerTestRunExtension.class)
执行MyTestClass
之前触发一个复杂的过程。
我创建了一个 Spring Boot 应用程序 (src/main/java
),它为我的测试 (src/test/java
) 提供了必要的数据。为测试准备数据可能需要几个小时。它还让我可以抽象地访问一些 rest-endpoints。
数据不会在所有测试类的过程之间发生变化。所以我只想拉一次数据。
只在一个类中编写所有测试是可行的,但我认为将它们分成不同的类可以提供更好的概览。
【问题讨论】:
【参考方案1】:在您自定义的BeforeAllCallback
的beforeAll(ExtensionContext)
方法中,您可以通过SpringExtension.getApplicationContext(extensionContext)
访问当前测试类的Spring ApplicationContext
。
如果您将自定义数据提供程序配置为该 ApplicationContext
中的 Spring 组件,则您可以从扩展中的 ApplicationContext
检索该组件 - 例如,通过 applicationContext.getBean(MyDataProvider.class)
。
如果您需要在测试之间处理数据并存储已处理的数据,您可以将其存储在 JUnit Jupiter 的 root ExtensionContext.Store
中。有关详细信息,请参阅ExtensionContext.getRoot()
和ExtensionContext.Store
中的getOrComputeIfAbsent(...)
变体。
【讨论】:
感谢您的反馈。到目前为止它对我有用。但是在我的测试中使用@Nested
类时发生了一些奇怪的事情。我通过SpringExtension.getApplicationContext(context).getBean(myBean.class)
在class SetupExtension implements BeforeAllCallback, AfterAllCallback
中获得了一个NoSuchBeanDefinitionException
Bean。仅当将测试放在嵌套类中时才会发生这种情况。如果我不使用嵌套测试(因此还有更多结构),我的测试虽然运行得很好。
关于 ExtensionContext.Store:这仅适用于扩展之间,对吗?我无法访问我在正常测试中存储在那里的任何数据。由于 BeforAllCallback 类应该收集数据,我需要访问它们。我的解决方案是在回调类中使用静态方法。但我更喜欢没有静态类的方式。
好吧,如果您的扩展程序保存在ExtensionContext.Store
中的某种“数据持有者”,您的扩展程序可以使该“数据持有者”可用于测试类——例如,通过依赖通过 ParameterResolver
扩展 API 注入。
关于@Nested
测试,请注意@Nested
测试类(尚未)从其封闭类继承Spring 测试配置。因此,您所经历的行为很可能是意料之中的。另见:github.com/spring-projects/spring-framework/issues/19930
这个链接很有帮助,谢谢!我用@SpringBootTest
注释了嵌套类并且它起作用了。令人惊讶的是,我认识到,实现BeforeAllCallback
的类中的beforeAll(ExtensionContext context)
方法在每个测试类之前被调用。我想存档,这个方法只被调用一次。因此我创建了一个static boolean started = false
来检查它是否需要再次执行。但这不是它的意图,对吧?有没有构建大型 JUnit 测试套件的好例子?【参考方案2】:
以下是我如何使用dataSource
Spring bean 在我的数据库中设置一些测试数据的方法,方法是将上面 Sam 的答案与以下答案相结合:https://***.com/a/51556718/278800
import javax.sql.DataSource;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
public class TestDataSetup implements BeforeAllCallback, ExtensionContext.Store.CloseableResource
private static boolean started = false;
private DataSource dataSource;
@Override
public void beforeAll(ExtensionContext extensionContext)
synchronized (TestDataSetup.class)
if (!started)
started = true;
// get the dataSource bean from the spring context
ApplicationContext springContext = SpringExtension.getApplicationContext(extensionContext);
this.dataSource = springContext.getBean(DataSource.class);
// TODO: put your one-time db initialization code here
// register a callback hook for when the root test context is shut down
extensionContext
.getRoot()
.getStore(ExtensionContext.Namespace.GLOBAL)
.put("TestDataSetup-started", this);
@Override
public void close()
synchronized (TestDataSetup.class)
// TODO: put your db cleanup code here
(我不能 100% 确定这个线程的安全性,所以为了安全起见,我添加了 synchronized
块。)
要启用此扩展,您只需将此注释添加到需要它的测试类中:
@ExtendWith(TestDataSetup.class)
好消息是 Junit 5 允许多个扩展,因此即使您的测试已经用 @ExtendWith(SpringExtension.class)
注释,它也可以工作。
【讨论】:
以上是关于JUnit 5:将 spring 组件注入 Extension (BeforeAllCallback / AfterAllCallback)的主要内容,如果未能解决你的问题,请参考以下文章
如何在用 Kotlin 编写的 JUnit 5 测试类中注入 Spring bean?
为啥我不能将此 Spring Boot 服务类注入 JUnit 测试类?预计至少有 1 个 bean 有资格作为 autowire 候选者