0

我的 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());
    }
}

虽然这基本上是有效的:我如何使用带smtpPort注释的@Value(从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没有拾取注释。

4

1 回答 1

1

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

相反,看看Spring 的测试执行侦听器抽象

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

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

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

更新 1

在问题中的更新 1 之后。这不起作用,因为侦听器本身不是 spring bean,因此您不能@Value在侦听器本身中自动装配或使用注释。您可以尝试关注这个可能有帮助的SO 线程,但最初我的意思是不同的:

  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方法,但在这种情况下,您必须在需要此行为的不同测试类中复制此逻辑。侦听器允许重用代码。

于 2020-09-02T06:54:55.607 回答