java 使用application.properties动态值覆盖Junit Test中默认的Spring-Boot www.example.com设置

2ledvvac  于 2023-03-21  发布在  Java
关注(0)|答案(4)|浏览(158)

我想在测试中覆盖application.properties中定义的属性,但@TestPropertySource只允许提供预定义的值。
我需要的是在一个随机端口N上启动一个服务器,然后将这个端口传递给spring-boot应用程序。这个端口必须是临时的,以便在同一个主机上同时运行多个测试。
我指的不是嵌入式http服务器(jetty),而是在测试开始时启动的其他服务器(例如zookeeper),被测应用程序必须连接到它。
实现这一目标的最佳途径是什么?
这里有一个类似的问题:

但是这些答案并没有提到临时端口的解决方案

whhtz7ly

whhtz7ly1#

从Spring Framework 5.2.5和Sping Boot 2.2.6开始,您可以在测试中使用Dynamic Properties

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

piv4azn72#

由于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);
    }
}
cbjzeqam

cbjzeqam3#

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

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

aiazj4mn4#

“干净”的解决方案是使用ApplicationContextInitializer
请参阅this answer来回答类似的问题。
另请参见this github issue询问类似问题。
使用一个真实世界的例子来总结上面提到的帖子,这个例子已经被消毒以保护版权所有者(我有一个REST端点,它使用@AutowiredDataSource,它需要使用动态属性来知道内存中的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方法只被调用一次,关闭钩子也是如此)。因此,它启动一个数据库,并让它运行,直到两个测试完成,然后关闭数据库。

相关问题