Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 ?

like image 703
Dimitri Kopriwa Avatar asked Nov 06 '17 21:11

Dimitri Kopriwa


Video Answer


1 Answers

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.

like image 152
Szymon Stepniak Avatar answered Sep 30 '22 09:09

Szymon Stepniak