简体   繁体   English

使用动态值覆盖 Junit 测试中的默认 Spring-Boot application.properties 设置

[英]Override default Spring-Boot application.properties settings in Junit Test with dynamic value

I want to override properties defined in application.properties in tests, but @TestPropertySource only allows to provide predefined values.我想在测试中覆盖 application.properties 中定义的属性,但 @TestPropertySource 只允许提供预定义的值。

What I need is to start a server on a random port N, then pass this port to spring-boot application.我需要的是在随机端口 N 上启动服务器,然后将此端口传递给 spring-boot 应用程序。 The port has to be ephemeral to allow running multiple tests on the same host at the same time.端口必须是临时的,以允许同时在同一主机上运行多个测试。

I don't mean the embedded http server (jetty), but some different server that is started at the beginning of the test (eg zookeeper) and the application being tested has to connect to it.我的意思不是嵌入式 http 服务器(jetty),而是在测试开始时启动的一些不同的服务器(例如zookeeper)并且被测试的应用程序必须连接到它。

What's the best way to achieve this?实现这一目标的最佳方法是什么?

(here's a similar question, but answers do not mention a solution for ephemeral ports - Override default Spring-Boot application.properties settings in Junit Test ) (这是一个类似的问题,但答案没有提到临时端口的解决方案 - 覆盖 Junit 测试中的默认 Spring-Boot application.properties 设置

As of Spring Framework 5.2.5 and Spring Boot 2.2.6 you can use Dynamic Properties in tests:从 Spring Framework 5.2.5 和 Spring Boot 2.2.6 开始,您可以在测试中使用Dynamic Properties

@DynamicPropertySource
static void dynamicProperties(DynamicPropertyRegistry registry) {
    registry.add("property.name", "value");
}

You could override the value of the port property in the @BeforeClass like this:您可以像这样覆盖@BeforeClass port 属性的值:

@BeforeClass
public static void beforeClass() {
    System.setProperty("zookeeper.port", getRandomPort());
}

Thanks to the changes made in Spring Framework 5.2.5, the use of @ContextConfiguration and the ApplicationContextInitializer can be replaced with a static @DynamicPropertySource method that serves the same purpose.由于 Spring Framework 5.2.5 中所做的更改,@ContextConfiguration 和 ApplicationContextInitializer 的使用可以替换为具有相同目的的静态 @DynamicPropertySource 方法。

@SpringBootTest
@Testcontainers
class SomeSprintTest {

    @Container
    static LocalStackContainer localStack = 
        new LocalStackContainer().withServices(LocalStackContainer.Service.S3);

    @DynamicPropertySource
    static void initialize(DynamicPropertyRegistry registry) {
        AwsClientBuilder.EndpointConfiguration endpointConfiguration = 
            localStack.getEndpointConfiguration(LocalStackContainer.Service.S3);

        registry.add("cloud.aws.s3.default-endpoint", endpointConfiguration::getServiceEndpoint);
    }
}

The "clean" solution is to use an ApplicationContextInitializer . “干净”的解决方案是使用ApplicationContextInitializer

See this answer to a similar question.请参阅对类似问题的回答。

See also this github issue asking a similar question.另请参阅此 github 问题,提出类似问题。

To summarize the above mentioned posts using a real-world example that's been sanitized to protect copyright holders (I have a REST endpoint which uses an @Autowired DataSource which needs to use the dynamic properties to know which port the in-memory MySQL database is using):使用一个真实世界的例子来总结上面提到的帖子,该例子已经过净化以保护版权所有者(我有一个 REST 端点,它使用一个@Autowired DataSource ,它需要使用动态属性来知道内存中 MySQL 数据库正在使用哪个端口):

  1. Your test must declare the initializer (see the @ContextConfiguration line below).您的测试必须声明初始化程序(请参阅下面的@ContextConfiguration行)。
// standard spring-boot test stuff
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("local")
@ContextConfiguration(
        classes = Application.class,
        // declare the initializer to use
        initializers = SpringTestDatabaseInitializer.class)
// use random management port as well so we don't conflict with other running tests
@TestPropertySource(properties = {"management.port=0"})
public class SomeSprintTest {
    @LocalServerPort
    private int randomLocalPort;

    @Value("${local.management.port}")
    private int randomManagementPort;

    @Test
    public void testThatDoesSomethingUseful() {
        // now ping your service that talks to the dynamic resource
    }
}
  1. Your initializer needs to add the dynamic properties to your environment.您的初始化程序需要将动态属性添加到您的环境中。 Don't forget to add a shutdown hook for any cleanup that needs to run.不要忘记为需要运行的任何清理添加关闭挂钩。 Following is an example that sets up an in-memory database using a custom DatabaseObject class.以下是使用自定义DatabaseObject类设置内存数据库的示例。
public class SpringTestDatabaseInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final int INITIAL_PORT = 0; // bind to an ephemeral port
    private static final String DB_USERNAME = "username";
    private static final String DB_PASSWORD = "password-to-use";
    private static final String DB_SCHEMA_NAME = "default-schema";

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        DatabaseObject databaseObject = new InMemoryDatabaseObject(INITIAL_PORT, DB_USERNAME, DB_PASSWORD, DB_SCHEMA_NAME);
        registerShutdownHook(databaseObject);
        int databasePort = startDatabase(databaseObject);
        addDatabasePropertiesToEnvironment(applicationContext, databasePort);
    }

    private static void addDatabasePropertiesToEnvironment(ConfigurableApplicationContext applicationContext, int databasePort) {
        String url = String.format("jdbc:mysql://localhost:%s/%s", databasePort, DB_SCHEMA_NAME);
        System.out.println("Adding db props to environment for url: " + url);
        TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
                applicationContext,
                "db.port=" + databasePort,
                "db.schema=" + DB_SCHEMA_NAME,
                "db.url=" + url,
                "db.username=" + DB_USERNAME,
                "db.password=" + DB_PASSWORD);
    }

    private static int startDatabase(DatabaseObject database) {
        try {
            database.start();
            return database.getBoundPort();
        } catch (Exception e) {
            throw new IllegalStateException("Failed to start database", e);
        }
    }

    private static void registerShutdownHook(DatabaseObject databaseObject) {
        Runnable shutdownTask = () -> {
            try {
                int boundPort = databaseObject.getBoundPort();
                System.out.println("Shutting down database at port: " + boundPort);
                databaseObject.stop();
            } catch (Exception e) {
                // nothing to do here
            }
        };

        Thread shutdownThread = new Thread(shutdownTask, "Database Shutdown Thread");
        Runtime.getRuntime().addShutdownHook(shutdownThread);
    }

}

When I look at the logs, it shows that for both of my tests that use this initializer class, they use the same object (the initialize method only gets called once, as does the shutdown hook).当我查看日志时,它表明对于我使用这个初始化器类的两个测试,它们使用相同的对象( initialize方法只被调用一次,关闭钩子也是如此)。 So it starts up a database, and leaves it running until both tests finish, then shuts the database down.所以它启动了一个数据库,让它一直运行直到两个测试完成,然后关闭数据库。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 Junit 测试中覆盖默认的 Spring-Boot application.properties 设置 - Override default Spring-Boot application.properties settings in Junit Test 用application.properties覆盖默认的Spring-Boot application.properties - Override default Spring-Boot application.properties with application.properties 覆盖application.properties以在Spring-boot应用程序中进行集成测试 - Override application.properties for integration tests in spring-boot app 如何在 Spring-Boot 的生产过程中覆盖 application.properties? - How to override application.properties during production in Spring-Boot? Spring-boot - 参数化测试 - 访问MethodSource中的Application.properties - Spring-boot - Parameterized Test - Access Application.properties in MethodSource Spring Boot 访问 application.properties 以进行 JUnit 测试 - Spring Boot accessing application.properties for JUnit Test 如何在Spring Boot集成测试中覆盖application.properties? - How to override application.properties in Spring Boot integration test? 在Junit测试中无法覆盖Spring-Boot应用程序属性 - Cannot overrid Spring-Boot application properties in Junit Test spring-boot application.properties中的环境变量错误 - Environment Variables in spring-boot application.properties error 春季启动application.properties问题-bug - spring-boot application.properties question -bug
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM