4

How we can programmatically configure Spring Boot to define new values to the spring.config.name and spring.config.location properties when running JUnit tests?

For example, if we would like to define these properties when running the application itself we could use something like (in Kotlin):

fun main(args: Array<String>) {
//  SpringApplication.run(Application::class.java, *args)

    val applicationContext = SpringApplicationBuilder(Application::class.java)
        .properties(
            """spring.config.name:
                ${getSpringConfigNames()}
            """,
            """spring.config.location:
                ${getSpringConfigLocationPaths()}
            """
        )
        .build()
        .run(*args)

//  val environment = applicationContext.getEnvironment()
}

But I wasn't able to find a way to configure this to use in the JUnit tests.

Edit

There is a complication here because of an spring boot limitation.

I would like to use an entire folder and its subfolders as valid locations to search for configuration files (so, for example, we could have folders for specific environments, databases, third-parties, and so on).

When running the application this was possible creating a method, in this case getSpringConfigLocationPaths(). And this method create a comma separated list with all folder inside the "main" folder.

For example, for the main folder src/main/resources/configuration it will output:

src/main/resources/configuration,
src/main/resources/configuration/environments,
src/main/resources/configuration/environments/development,
src/main/resources/configuration/environments/staging,
src/main/resources/configuration/environments/testing,
src/main/resources/configuration/environments/production,
src/main/resources/configuration/environments/common

How could we solve this situation when using JUnit tests and Spring Boot?

Unfortunately Spring Boot doesn't allow something like src/main/resources/configuration/**/*.

Because we have organized the system with several properties files on different subfolders we need to find a way to dinamically consider them.

GarouDan
  • 3,743
  • 9
  • 49
  • 75

3 Answers3

3

I am using latest Spring Boot 2.2.0 and from my experience both @TestPropertySource and @SpringBootTest annotations can do the job because they have properties attribute. So, you can do something like this:

@TestPropertySource(properties = {"spring.config.location=classpath:dev/", "spring.config.name=custom-app-name"}
@TestConfiguration
class DevTestCfg {} // this will make tests to look for configs in resources/dev/custom-app-name.properties

Also notice that there is a spring.config.additional-location property if you want your properties to be loaded from multiple locations.

The only problem here is that values in properties attribute must be constant. But you can create multiple configurations for each environment and put corresponding @Profile("envName") on each configuration class. Then run your tests with different -Dspring.profiles.active and corresponding test configuration should be automatically picked up.

Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
Kirill
  • 6,762
  • 4
  • 51
  • 81
2

The tests that run spring boot should be carefully designed, There is a whole testing framework for spring boot tests, so obviously consider using this framework.

When it comes to configuration management, I suggest considering the following:

There are two types of tests basically:

  • Tests that load a concrete specific configuration (set of beans), for example if you want to test only a DAO, you load a configuration for this dao. In this case, the configuration is something that should be "tailored" to the needs of a specific test, and no "full" configuration is required.

For example, if the microservice contains a configuration for a database (user, password, schema, etc) and for, say, messaging management, there is no need to specify a configuration of a messaging system when testing a DAO, messaging beans won't be loaded anyway.

Usually, the test of this "type" will look like this:

@SpringBootTest(classes = {RelationalDbDaoConfiguration.class}) 
public class MyDaoTest {

}

If you don't have a configuration for your needs you can use @MockBean to mock unnecessary beans or even create a custom configuration in src/test/java so that it will be only in test classpath. It makes sense to use @TestConfiguration but it's beyond the scope of the question.

Now in order to load the configuration for db only, the are many options, to name a few:

  • @ActiveProfiles("dao") on a test class + putting "application-dao.properties/yaml" into the src/test/resources or src/test/resources/config

  • Use @TestPropertySource(locations = "classpath:whatever.properties") on test

  • Create a special "DbProperties" bean and initialize it programmatically in spring, it can make sense when you know some details about the context in which the test runs only during the actual test execution (for example, if you start a database before the test and the port is created dynamically, but its really a fairly advanced setup and is beyond the scope of this question) + the data source bean can read these properties

  • Use @SpringBootTest's properties attribute to provide 'fine-grained' properties definitions

  • Kind of obvious, but I'll mention it anyway: put application.properties in src/test/resources it will override regular configurations

The second type of tests is when you load the "entire" microservice, usually, these are tests that do not have "classes" parameter in @SpringBootTest annotation

@SpringBootTest // note, no actual configurations specified
public class MyMicroserviceTest {
  ...
}

Now, this definitely requires to specify a whole set of configurations, although the techniques for actually specifying these configurations are still applicable (just the content of configuration files will be different).

I do not suggest the usage of spring.config.location during the test, because this means that the test depends on some external resource, which makes the whole setup even more complicated.

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • Hi Mark, thanks for the solution. I should have explained a bit more why I needing this (and the main problem is related with the dynamic folders to be considered in the spring.config.location property). Do you know if we can do something about it? – GarouDan Sep 03 '18 at 14:05
  • Well, for example in @TestPropertySource approach there is a 'location' attribute so you can specify the configuration – Mark Bramnik Sep 03 '18 at 18:15
1

If it's XML driven configuration,

@ContextConfiguration(locations = "/app-context.xml")

If it's annotation driven by configuration classes,

@ContextConfiguration(classes = {AppCOnfig::class, AnotherCOnfig::class}

These would be defined on the class level on the unit test class you run.

Further, if you have profiles for Junit to consider, @ActiveProfiles("myProfile") would be added to the test class.

Karthik R
  • 5,523
  • 2
  • 18
  • 30
  • Hi Karthik R, thanks for the solution. I should have explained a bit more why I needing this (and the main problem is related with the dynamic folders to be considered in the spring.config.location property). Do you know if we can do something about it? – GarouDan Sep 03 '18 at 14:05
  • I saw your updated question. You don't need to give multiple files to spring boot test. I understand what you are trying to do. You are trying to test all environment configurations. But unit tests are not supposed to do that. You can include one profile within another by using `spring.profiles.include` , but this will create conflicts if you have same configurations repeated across. – Karthik R Sep 04 '18 at 08:42
  • If your goal here is to try to test all the environment configurations, I wouldn't recommend you to try that. Your configurations based on the profiles(dev, qa etc) should only vary on external parameters like DB url etc., but your unit tests should never worry about it. Either mock these external factors or just test it for one environment, say local. I hope I didn't misunderstand your question. – Karthik R Sep 04 '18 at 08:46