59

I want to test small parts of the application that rely on properties loaded with @Autowired and @ConfigurationProperties. I am looking for a solution loading only the required properties and not always the whole ApplicationContext. Here as reduced example:

@TestPropertySource(locations = "/SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestSettings.class, TestConfiguration.class})
public class SettingsTest {
    @Autowired
    TestConfiguration config;

    @Test
    public void testConfig(){
        Assert.assertEquals("TEST_PROPERTY", config.settings().getProperty());
    }
}

Configuration Class:

public class TestConfiguration {
    @Bean
    @ConfigurationProperties(prefix = "test")
    public TestSettings settings (){
        return new TestSettings();
    }
}

Settings Class:

public class TestSettings {
    private String property;

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }
}

The properties file in the resource folder contains the entry:

test.property=TEST_PROPERTY

In my current setup config is not null, but no fields are available. The reason the fields are not field should have something to do with the fact that I am not using Springboot but Spring. So what would be the Springboot way to get this running?

edit: The reason why I want to do this is: I have a parser that parses Textfiles, the regular expressions used are stored in a properties file. To test this I would like to load only the properties needed for this parser which are in the exaple above the TestSettings.

While reading the comments I already noticed that this are no Unit tests anymore. However using the full Spring boot configuration for this small test seems a bit too much to me. That's why I asked if there is a posibilty to load only the one class with properties.

Ihor Patsian
  • 1,288
  • 2
  • 15
  • 25
IndianerJones
  • 985
  • 1
  • 8
  • 23
  • You could try to use Mockito's [`Whitebox.setInternalState(object, fieldName, value)`](http://docs.mockito.googlecode.com/hg/org/mockito/internal/util/reflection/Whitebox.html) method to "inject" values into an object. [Example](https://code.google.com/p/mockito/source/browse/test/org/mockito/internal/util/reflection/WhiteboxTest.java?r=9772247b067621ed5c3cefc356397b0bde5b89f6) – Roman Vottner Jul 31 '15 at 11:54
  • You could have a look how [`MockitoAnnotations.initMocks(java.lang.Object)`](http://docs.mockito.googlecode.com/hg/org/mockito/MockitoAnnotations.html#initMocks(java.lang.Object)) works and extend that to work for `@ConfigurationProperties` and `@Autowired` (may already work for that, but I'm not sure). – SpaceTrucker Jul 31 '15 at 12:02
  • @Roman Vottner: Thanks for the hint. The issue is, It's not only one field I want to mock, it's 10. So it's would be bit writing effort. That would be nothing to complain about, if it's only that unit test, but I want to have lots of them, so having a more easy way would be really helpful. – IndianerJones Jul 31 '15 at 12:08
  • If you "inject" the same kind of objects for a couple of tests, just refactor the instruction to a helper method and call this method in those tests. You can also parametreize this method to inject certain objects. You can also think about a kind of builder setup that "injects" default values if no builder specific method is called and sets a concrete inject-value if a respecive builder-method was set. – Roman Vottner Jul 31 '15 at 12:14
  • What are you trying to do exactly? From the example I saw it looks like you're testing the framework (you shouldn't). You may be interested by this answer http://stackoverflow.com/questions/31692863/what-is-the-best-way-to-test-that-a-spring-application-context-fails-to-start/31722400#31722400 – Stephane Nicoll Jul 31 '15 at 15:32

5 Answers5

50

You need to annotate your TestConfiguration class with @EnableConfigurationProperties as follows:

@EnableConfigurationProperties
public class TestConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "test")
    public TestSettings settings (){
        return new TestSettings();
    }
}

Also you only need to include TestConfiguration.class in @ContextConfiguration of your SettingsTest class:

@TestPropertySource(locations = "/SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
public class SettingsTest {
...
TCH
  • 421
  • 1
  • 6
  • 25
Stepan Kolesnik
  • 791
  • 1
  • 7
  • 12
  • 13
    If you working with Spring Boot and with `*.yml` files, just remove `@TestPropertySource` and add one special initializer to `@ContextConfiguration` as follows: `@ContextConfiguration(classes = TestConfiguration.class, initializers = ConfigFileApplicationContextInitializer.class)` – Rib47 Feb 15 '18 at 17:38
  • never forget to put setter and getters for the collections ;) – Tama Jan 13 '19 at 14:14
  • 1
    https://www.baeldung.com/properties-with-spring section 5.3 has also good example alternatively to the above explanation – Tugrul ASLAN Jan 28 '19 at 15:55
  • 1
    @Rib47 i think it's called `@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)` now – Valerij Dobler Aug 17 '22 at 16:32
17

you can actually just add @EnableConfigurationProperties to your @SpringBootTest directly.
eg:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestConfiguration.class)
@EnableConfigurationProperties
...
Jason
  • 526
  • 7
  • 9
  • '@EnableConfigurationProperties helped in my case. I just using '@RunWith(SpringJUnit4ClassRunner.class) - test runner '@TestPropertySource() to redefine properties for test '@EnableConfigurationProperties(UnitProperties.class) - to allow initialization of config bean marked with @ConfigurationProperties. – Oleksandr_DJ Feb 26 '20 at 08:47
16

A couple points:

  1. You don't need a "TestConfiguration" class in your main package, because all it's doing is configuring the "TestSettings" bean. You can do this simply by annotating the TestSettings class itself.

  2. Normally you would load the context you need for the test using the @SpringApplicationConfiguration annotation, passing the name of your Application class. However, you said you don't want to load the whole ApplicationContext (though it's not clear why), so you need to create a special configuration class to do the loading only for tests. Below I call it "TestConfigurationNew" to avoid confusion with the TestConfiguration class that you had originally.

  3. In the Spring Boot world, all properties are generally kept in the "application.properties" file; but it is possible to store them elsewhere. Below, I have specified the "SettingsTest.properties" file that you proposed. Note that you can have two copies of this file, the one in the main/resources folder, and the one in the test/resources folder for testing.

Change the code as follows:

TestSettings.java (in main package)

@Configuration
@ConfigurationProperties(prefix="test", locations = "classpath:SettingsTest.properties")
public class TestSettings {

    private String property;

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }
}

SettingsTest.java (in test package)

@TestPropertySource(locations="classpath:SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfigurationNew.class)
public class SettingsTest {

    @Autowired
    TestSettings settings;

    @Test
    public void testConfig(){
        Assert.assertEquals("TEST_PROPERTY", settings.getProperty());
    }
}

TestConfigurationNew.java (in test package):

@EnableAutoConfiguration
@ComponentScan(basePackages = { "my.package.main" })
@Configuration
public class TestConfigurationNew {
}

This should now work the way you wanted.

David H
  • 1,461
  • 2
  • 17
  • 37
  • 6
    There can be a couple of reasons why a Spring context should not be created while unit-testing. One of these is simply the fact, that including a Spring context while testing is not a unit-test but an integration test. Running a test with Spring context (especially if dirties context is set to after each method) needs way more time to execute than a proper unit-test. Furthermore, testing edge-cases like a failed injection for a single test method is harder to setup using an enabled Spring context. – Roman Vottner Jul 31 '15 at 14:46
  • 2
    @RomanVottner - I agree, but once the OP indicated he is checking whether or not a setting loads correctly from a properties file, I would argue that he has moved into the realm of integration testing and now needs a fuller context. – David H Jul 31 '15 at 15:34
  • I'm getting java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124 – Marcello DeSales May 10 '16 at 02:19
  • 1
    Is it possible to load YML instead of properties ? – Dimitri Kopriwa Jun 12 '17 at 13:10
  • This doesn't solve the problem posed by the OP, only providing an alternative – Olayinka Dec 20 '18 at 11:48
  • @RomanVottner how running test with Spring context makes it integration? Testing pyramid and spring context are something not-related. If I test single Spring component (service, repository, etc) and I want to use Spring context features like configuration, it does not make this test integration. – stinger Jul 22 '22 at 13:32
  • @stinger Please review the definition of unit, integration and end-to-end tests. If you have some test code that spins up a Spring context, you defacto test your code's behavior within the Spring framework and not your code in isolation. That makes the test by definition an integration test and not a unit test. Depending on the stuff you configure it may already be an end-to-end test, i.e. if you spin up or connect to real databases or the like. Sure, in the end it is just a naming thing, though spinning up Spring takes some time which is not always needed to test your component – Roman Vottner Jul 23 '22 at 08:46
  • @RomanVottner Spring is just a way of writing and running code (DI framework, basically). You now mixing concepts of integration of business parts of your code (database, APIs, other service layers) with concept of helping you to inject dependencies. Ultimately, you can instantiate test or mock objects using keyword `new`, without Spring, so does this mean you convert test from IT to unit ? – stinger Aug 09 '22 at 12:57
  • @stinger as mentioned before, our discussion spins around nomenclature, but running a real unit-test vs. a Spring-enhanced one will outperform the latter usually by a large margin as spinning up the Spring context usually has its price and may also require certain attention in terms of what is present in that context at which time (-> dirty context and so forth). Even Spring itself recommends for years now to switch to constructor or method injection rather then property injection as such "beans" can be tested without a need for Spring at all – Roman Vottner Aug 09 '22 at 13:41
9

If you use Spring Boot, now you only need:

@RunWith(SpringRunner.class)
@SpringBootTest

No extra @ContextConfiguration, no extra class only for tests to EnableAutoConfiguration and EnableConfigurationProperties. You don't have to specify the configuration class to load, they will all be loaded.

But, ensure the properties entries you want to read in main/resources/application.yml is also present in test/resources/application.yml. Repetition is unavoidable.


Another way is:

  1. Define a class of configuration only for tests, along with MyApplicationTest.java, at the same level. This class can be empty.

Like:

@EnableAutoConfiguration
@EnableConfigurationProperties(value = {
        ConnectionPoolConfig.class
})
public class MyApplicationTestConfiguration {
}
  1. And, in the class to load the autowired configuration.

Like:

@RunWith(SpringRunner.class)
//@SpringBootTest // the first, easy way
@ContextConfiguration(classes = MyApplicationTestConfiguration.class,
        initializers = ConfigFileApplicationContextInitializer.class)
public class ConnectionPoolConfigTest {

    @Autowired
    private ConnectionPoolConfig config;

Basically, you:

  • use a specific configuration to @EnableConfigurationProperties and @EnableAutoConfiguration, listing all the @ConfigurationProperties files you want to load
  • in the test class, you load this configuration file of tests, with an initializer class defined by Spring to load application.yml file.

And, put the values to load in test/resources/application.yml. Repetition is unavoidable. If you need load another file, use @TestProperties() with a location. Note: @TestProperties only supports .properties files.


Both way works for configuration class loading values

  • either from application.yml/application.properties
  • or from another properties file, specified by PropertySource, like @PropertySource(value = "classpath:threadpool.properties")

Important

Last notes from Spring doc, as per here

Some people use Project Lombok to add getters and setters automatically. Make sure that Lombok does not generate any particular constructor for such a type, as it is used automatically by the container to instantiate the object.

Finally, only standard Java Bean properties are considered and binding on static properties is not supported.

That means, if you have lombok.@Builder without @NoArgsConstructor nor @AllArgsConstructor, properties injection will not happen because it only sees the invisible constructor created by @Builder. So, be sure to use none, or all of these annotations!

WesternGun
  • 11,303
  • 6
  • 88
  • 157
  • If you don't need to everything what `@SpringBootTest` provides you should not use it. And I thinkt that testing properties are this case when is better don't use it. You can use `@SpringJUnitConfig` instead of it. – Saljack Oct 15 '20 at 07:35
  • The second approach does not use `SpringBootTest` – WesternGun Oct 15 '20 at 08:24
  • 1
    Awesome mass of good information in a so little post ! the key point for me was "And, put the values to load in test/resources/application.yml. Repetition is unavoidable" – alban maillere Apr 15 '21 at 11:25
4

Unit test

To avoid having to load a Spring context, we can use the Binder class, which is also used internally by Spring anyway.

// A map of my properties.
Map<String, String> properties = new HashMap<>();
properties.put("my-prefix.first-property", "foo");
properties.put("my-prefix.second-property", "bar");

// Creates a source backed by my map, you can chose another type of source as needed.
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties)

// Binds my properties to a class that maps them.
Binder binder = new Binder(source);
BindResult<MyConfiguration> result = binder.bind("my-prefix", MyConfiguration.class);

// Should return true if bound successfully.
Assertions.assertTrue(result.isBound);

// Asserts configuration values.
MyConfiguration config = result.get();
Assertions.assertEquals("foo", config.getFirstProperty());
Assertions.assertEquals("bar", config.getSecondProperty());
Gustavo Passini
  • 2,348
  • 19
  • 25