How to load resource properties in a JUnit Spring context in a multi Gradle project?

Dimitri Kopriwa :

I have the following project tree:

├── app
│   ├── build.gradle
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── child
│       │   │       └── app
│       │   │           └── Application.java
│       │   └── resources
│       │       └── application-default.yaml
│       └── test
│           └── java
│               └── child
│                   └── app
│                       └── ApplicationTest.java
├── build.gradle
├── childA
│   ├── build.gradle
│   └── src
│       └── main
│           └── java
│               └── child
│                   └── a
│                       ├── BaseGreeterImpl.java
│                       ├── ChildAConfig.java
│                       ├── Greeter.java
│                       └── MySpringProperties.java
├── childB
│   ├── build.gradle
│   └── src
│       └── main
│           └── resources
│               ├── application-test.yaml
│               └── childB.properties
├── childC
│   ├── build.gradle
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── child
│       │   │       └── c
│       │   │           ├── ChildCConfig.java
│       │   │           └── PropertyGreeterImpl.java
│       │   └── resources
│       │       └── childc.properties
│       └── test
│           └── java
│               └── child
│                   └── c
│                       ├── TestYamlImport.java
│                       └── TestGreeter.java
└── settings.gradle

I have the following test class :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ChildCConfig.class }, loader = AnnotationConfigContextLoader.class)
@ActiveProfiles("test")
@SpringBootTest
public class TestYamlImport {

    @Autowired
    private MySpringProperties properties;

    @Test
    public void readChildAYaml() {
        assertThat(properties.getName()).isEqualTo("it-is-another-thing");
    }

}

Expected

I expect properties.getName() to read the value from resource childB in childB/src/main/resources/application-test.yaml.

Result

I get null

Reproduction

GitHub: https://github.com/kopax/adk/tree/adk-spring

One liner:

git clone [email protected]:kopax/adk.git && cd adk && git checkout adk-spring && ./gradlew build --info

Question

There is a test called childC/src/test/java/childC/TestGreeter.java in the reproduction project which prove with childB.properties import that it is not a classpath issue.

So here are my questions :

  • Is spring limiting the classpath resolution somehow when using @ConfigurationProperties ?

  • I haven't found a way to read my application-test.ymlwithin a configuration @Bean initialized in childA from the test scope of childB, how is this possible ?

Szymon Stepniak :

Is there any particular reason you are using AnnotationConfigContextLoader instead of (default) SpringBootContextLoader? The problem you are facing is not caused by file missing in the classpath (you can copy application-test.yaml to any src/main/resources or src/test/resources with the same result) but the fact that AnnotationConfigContextLoader does not use ConfigFileApplicationListener that is responsible for configuring context by loading properties from well known file locations (like application-{profile}.yaml in your case).

You can easily compare what properties are loaded when using each loader. Firstly you can check what AnnotationConfigContextLoader does - just put a breakpoint at line 128 of AbstractGenericContextLoader.java file and run debugger in your favorite IDE:

enter image description here

Next you can investigate variable context -> environment -> propertySources -> propertySourceList. You will find 5 property sources:

enter image description here

None of them loads properties from config files like application.yml or application.properties.

Now let's do the same but with SpringBootContextLoader class. Firstly remove

loader = AnnotationConfigContextLoader.class

in MyEntityTest and put a breakpoint at line 303 in SpringApplication.java file:

enter image description here

Here we are right before application context gets refreshed. Now let's investigate variable context -> environment -> propertySources -> propertySourceList:

enter image description here

The first difference we can see is that now we have 7 property sources instead of 5 as it was in the previous example. And what is most important - ConfigFileApplicationListener.ConfigurationPropertySources is here. This class makes application context aware of application-{profile}.yaml properties file existence.

enter image description here

So as you can see it is only a matter of using correct context loader. Replace

@ContextConfiguration(classes = { ChildCConfig.class }, loader = AnnotationConfigContextLoader.class)

with

@ContextConfiguration(classes = { ChildCConfig.class }, loader = SpringBootContextLoader.class)

or

@ContextConfiguration(classes = { ChildCConfig.class })

as this loader is a default one when using @SpringBootTest annotation and you will make your test passing like a charm. I hope it helps.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=462906&siteId=1