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

时间:2015-06-25 18:51:20

标签: java spring junit spring-boot automated-tests

I want to override properties defined in application.properties in tests, but @TestPropertySource only allows to provide predefined values. What I need is to start a server on a random port N, then pass this port to spring-boot application. 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 (e.g. zookeeper) and the application being tested has to connect to it. 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)

4 个答案:

答案 0 :(得分:3)

您可以覆盖@BeforeClass中port属性的值,如下所示:

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

答案 1 :(得分:2)

由于在Spring Framework 5.2.5中进行了更改,因此可以使用具有相同目的的静态@DynamicPropertySource方法来替换@ContextConfiguration和ApplicationContextInitializer的使用。

@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);
    }
}

答案 2 :(得分:1)

“干净”的解决方案是使用ApplicationContextInitializer

参见this answer,了解类似问题。

另请参见this github issue提出类似问题。

使用经过处理以保护版权持有者的真实示例总结上述文章(我有一个REST端点,该端点使用@Autowired DataSource,需要使用动态属性来知道内存MySQL数据库正在使用哪个端口):

  1. 您的测试必须声明初始化程序(请参见下面的@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. 您的初始化程序需要将动态属性添加到您的环境中。不要忘记为需要运行的任何清理添加一个关闭钩子。以下是使用自定义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);
    }

}

当我查看日志时,它表明对于使用该初始化器类的两个测试,它们都使用相同的对象(initialize方法仅被调用一次,关机钩子也被调用一次)。因此,它将启动数据库,并使其保持运行状态,直到两个测试都完成,然后关闭数据库。

答案 3 :(得分:1)

从Spring Framework 5.2.5和Spring Boot 2.2.6开始,您可以在测试中使用docker-compose run python python stress_test.py

Dynamic Properties