javaspring-boottestingjunit5

How to load properties from application.properties with @ExtendWith(SpringExtension.class)


I am trying to learn Spring Boot and I was trying to get a solid understanding about testing.

I am aware that @SpringBootTest loads the full context I do not want to do that. I know that the slice tests (i.e: @WebMvcTest, @JdbcTest) internally use @ExtendWith(SpringExtension.class), What if i just want a simple integrated test with my own beans and I also want to load configuration properties, is it possible to use this with the @Value ?

I know I can use ReflectionTestUtils but I thought since Spring Extension loads a test context, it should get the application.properties file ?

So far I am getting a very strange result with @Value, it gets the key instead of the value. It works perfectly with @SpringBootTest so I know the properties are loaded fine when the full application.

Here is my code:

application.properties in src/test/resources:

misc.test-prop-value=testPropValue

@ExtendWith(SpringExtension.class) public class ConfigPropertiesTest {

@Value( value = "${misc.test-prop-value}")
private String testPropValue;


@Value("${some.non.existent.value}")
private String nonExistentValue;

@Test
void testNonExistentValue() {
    assertNull(nonExistentValue); // Fails, the value is: ${some.non.existent.value}
}

@Test
public void testConfigProperties() {
    assertEquals("testPropValue", testPropValue); //Fails, the value is: ${misc.test-prop-value}
}

}

Why is the @Value picking up the key itself ?

It works perfectly with @SpringBootTest,

I thought since @SpringExtension loads an application context:

@ExtendWith(SpringExtension.class)
public class SpringExtensionBasicTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void contextLoads() throws Exception {
        //GenericApplicationContext instance:
        System.out.println("Context instance: " + applicationContext.getClass());
        assertNotNull(applicationContext); // Test passes
    }



}

When I extend a base class that has @SpringBootTest, Everything works fine, it loads the context, and there is even an error when the some.non.existent.value property not found. But with the Spring Extension, the behaviour is weird.

Maybe this is a silly question, I can sense this is one of those things, where I am missing something so obvious in my face that no one has even explained it, but i am not getting it. Also if I wanted my own slice test but I still want the .properties file to be loaded like it does with @JdbcTest for example, how would this work

Edit:

I got two of the tests to pass when using @TestPropertySource, but the non existent value that is null with @SpringBootTest, still shows the same key:

@ExtendWith(SpringExtension.class)
@TestPropertySource(value = "classpath:application.properties")
public class ConfigPropertiesTest  {

    @Value( value = "${misc.test-prop-value}")
    private String testPropValue;

    @Value("${family.mother}")
    private String mamaValue;

    /**
     * When a non-existent value is there,
     * It is not null for some strange reason,
     * But it still shows the key
     */
    @Value("${some.non.existent.value}")
    private String nonExistentValue;

    @Test
    void testNonExistentValue() {
        //Still shows ${some.non.existent.value}
        assertNull(nonExistentValue);
    }

    @Test
    void testPropValue1() {
        assertEquals("Mama-test", mamaValue); //Ok
    }

    @Test
    public void testConfigProperties() {
        assertEquals("testPropValue", testPropValue); //Ok
    }

}

Solution

  • I think your IgnoreUnresolvablePlaceholders parameter is set to true. Otherwise, the absence of the some.non.existent.value property would result in an error

    java.lang.IllegalArgumentException: Could not resolve placeholder 'some.non.existent.value' in value "${some.non.existent.value}"
    

    For example, if you create a bean like this:

        @Bean
        public PropertySourcesPlaceholderConfigurer propertyConfig() {
            PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
            configurer.setIgnoreUnresolvablePlaceholders(true);
            return configurer;
        }
    

    then if Spring cannot find the some.non.existent.value property for the nonExistentValue field, it will write string "${some.non.existent.value}" to that field. I think it makes sense for you to set a default value for the nonExistentValue field using this code:

        @Value("${some.non.existent.value:#{null}}")
        private String nonExistentValue;