【问题标题】:Passing an external property to JUnit's extension class将外部属性传递给 JUnit 的扩展类
【发布时间】:2020-09-01 21:32:38
【问题描述】:

我的 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 注释。

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

    请注意,请确保您将mergeMode = MergeMode.MERGE_WITH_DEFAULTS 作为@TestExecutionListeners 注释的参数,否则某些默认行为(如测试中的自动装配、如果有的话是脏上下文等)将不起作用。

    更新 1

    在问题更新 1 之后。这不起作用,因为侦听器本身不是 spring bean,因此您不能在侦听器本身中自动装配或使用 @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>/ 中,这样在生产中就不会加载它了

    1. 现在测试执行侦听器只能用于运行启动/停止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();
        }
        
    }
    

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

    【讨论】:

    • 感谢您的提示。我用你提出的方法更新了我原来的问题,但仍然有一些东西不起作用。你介意看看吗?
    • @RobertStrauch:我已经更新了答案,如果有帮助请告诉我...
    • 非常感谢。您的更新 1 正是我想要的。这帮助我让它发挥作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-27
    • 2016-10-20
    • 2018-03-25
    • 2014-11-14
    • 2012-04-16
    相关资源
    最近更新 更多