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

前端 未结 4 1705
挽巷
挽巷 2020-12-10 16:56

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

相关标签:
4条回答
  • 2020-12-10 17:18

    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.

    @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);
        }
    }
    
    0 讨论(0)
  • 2020-12-10 17:20

    You could override the value of the port property in the @BeforeClass like this:

    @BeforeClass
    public static void beforeClass() {
        System.setProperty("zookeeper.port", getRandomPort());
    }
    
    0 讨论(0)
  • 2020-12-10 17:21

    As of Spring Framework 5.2.5 and Spring Boot 2.2.6 you can use Dynamic Properties in tests:

    @DynamicPropertySource
    static void dynamicProperties(DynamicPropertyRegistry registry) {
        registry.add("property.name", "value");
    }
    
    0 讨论(0)
  • 2020-12-10 17:27

    The "clean" solution is to use an ApplicationContextInitializer.

    See this answer to a similar question.

    See also this github issue asking a similar question.

    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):

    1. Your test must declare the initializer (see the @ContextConfiguration line below).
    // 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.
    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). So it starts up a database, and leaves it running until both tests finish, then shuts the database down.

    0 讨论(0)
提交回复
热议问题