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】:

在您自定义的BeforeAllCallbackbeforeAll(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 候选者

JUnit 5 Jupiter API

spring入门-整合junit和web

Spring Boot 中的 Junit 测试不注入服务

Spring