spring-boot - 将外部属性传递给 JUnit 的扩展类

标签 spring-boot junit junit5 junit5-extension-model

我的 Spring Boot 项目使用 JUnit 5。我想设置一个需要启动本地 SMTP 服务器的集成测试,因此我实现了一个自定义扩展:

public class SmtpServerExtension implements BeforeAllCallback, AfterAllCallback {

    private GreenMail smtpServer;
    private final int port;

    public SmtpServerExtension(int port) {
        this.port = port;
    }

    @Override
    public void beforeAll(ExtensionContext extensionContext) {
        smtpServer = new GreenMail(new ServerSetup(port, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
        smtpServer.start();
    }

    @Override
    public void afterAll(ExtensionContext extensionContext) {
        smtpServer.stop();
    }
}

因为我需要配置服务器的端口,所以我在测试类中注册扩展,如下所示:

@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
public class EmailControllerIT {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Value("${spring.mail.port}")
    private int smtpPort;

    @RegisterExtension
    // How can I use the smtpPort annotated with @Value?
    static SmtpServerExtension smtpServerExtension = new SmtpServerExtension(2525);

    private static final String RESOURCE_PATH = "/mail";
    
    @Test
    public void whenValidInput_thenReturns200() throws Exception {
        mockMvc.perform(post(RESOURCE_PATH)
                .contentType(APPLICATION_JSON)
                .content("some content")
        ).andExpect(status().isOk());
    }
}

虽然这基本上可以工作:如何使用用 @Value 注释的 smtpPort (从 test 配置文件中读取)?


更新 1

根据您的建议,我创建了一个自定义TestExecutionListener

public class CustomTestExecutionListener implements TestExecutionListener {

    @Value("${spring.mail.port}")
    private int smtpPort;

    private GreenMail smtpServer;

    @Override
    public void beforeTestClass(TestContext testContext) {
        smtpServer = new GreenMail(new ServerSetup(smtpPort, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
        smtpServer.start();
    };

    @Override
    public void afterTestClass(TestContext testContext) {
        smtpServer.stop();
    }
}

监听器的注册如下:

@TestExecutionListeners(value = CustomTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)

运行测试时,监听器被调用,但 smtpPort 始终为 0,因此似乎未选择 @Value 注释向上。

最佳答案

我认为你不应该在这里使用扩展,或者一般来说,任何“原始级别”JUnit 的东西(比如生命周期方法),因为你将无法从它们访问应用程序上下文,不会'无法在 bean 等上执行任何自定义逻辑。

相反,请查看 Spring's test execution listeners abstraction

通过这种方法,GreenMail将成为一个由 spring 管理的 bean(可能在一个仅在测试中加载的特殊配置中),但由于它成为一个 bean,它将能够加载属性值并使用 @Value注释。

在测试执行监听器中,您将在测试之前启动服务器并在测试之后停止(如果需要,则启动整个测试类 - 它有“钩子(Hook)”)。

附注,请确保 mergeMode = MergeMode.MERGE_WITH_DEFAULTS作为 @TestExecutionListeners 的参数注解,否则某些默认行为(例如测试中的 Autowiring 、脏上下文(如果有的话)等)将无法工作。

更新1

以下问题中的更新 1。这不起作用,因为监听器本身不是 spring bean,因此您无法 Autowiring 或使用 @Value监听器本身的注释。 您可以尝试关注this SO thread这可能会有所帮助,但最初我的意思有所不同:

  1. 使 GreenMail 本身成为一个 bean:
@Configuration 
// since you're using @SpringBootTest annotation - it will load properties from src/test/reources/application.properties so you can put spring.mail.port=1234 there 
public class MyTestMailConfig {

   @Bean
   public GreenMail greenMail(@Value(${"spring.mail.port"} int port) {
      return new GreenMail(port, ...);
   }
}

现在这个配置可以放在 src/test/java/<sub-package-of-main-app>/ 中这样在生产中它就不会被加载

  • 现在测试执行监听器只能用于运行启动/停止 GreenMail服务器(据我了解,您希望在测试之前启动它并在测试之后停止,否则您根本不需要这些监听器:))
  • public class CustomTestExecutionListener implements TestExecutionListener {
    
        @Override
        public void beforeTestClass(TestContext testContext) {
           GreenMail mailServer = 
                testContext.getApplicationContext().getBean(GreenMail.class);
                mailServer.start();
        } 
    
        @Override
        public void afterTestClass(TestContext testContext) {
           GreenMail mailServer = 
                testContext.getApplicationContext().getBean(GreenMail.class);
                mailServer.stop();
        }
        
    }
    

    另一个选项是 Autowiring GreenMail bean 并使用@BeforeEach@AfterEach JUnit 的方法,但在这种情况下,您必须在需要此行为的不同测试类中复制此逻辑。监听器允许重用代码。

    关于spring-boot - 将外部属性传递给 JUnit 的扩展类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63695727/

    相关文章:

    Java Spring Security - User.withDefaultPasswordEncoder() 已弃用?

    java - 使用 Spring Boot 处理事务隔离级别

    security - Spring Boot 始终使用最新的 tomcat 版本

    java - JUnit:是否可以创建一个测试套件来执行共享命名约定的类的所有测试?

    java - Hamcrest 比较集合

    spring-boot - 使用 mvn spring-boot :run "A child container fail to start" 出错

    java - 如何仅在运行某些特定单元测试时才命中断点?

    java - Springboot测试在Junit 5中加载ApplicationContext失败

    java - 如何使用自定义@Rule正确配置@RunWith(Parameterized.class) + SpringClassRule + SpringMethodRule?

    java - 为什么使用 junit-jupiter(聚合器)执行测试而不使用单独的依赖项?