我的 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这可能会有所帮助,但最初我的意思有所不同:
- 使 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/