JUnit: Can I keep the common parts of the spring context? - java

I have some unit tests with Spring. All the tests load a spring configuration file, and then add some more.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:beans.xml" }, inheritLocations = true)
public abstract class TestBase {
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:extraBeansOne.xml" }, inheritLocations = true)
public class TestOne extends TestBase {
#Test
public void testA() {
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:extraBeansTwo.xml" }, inheritLocations = true)
public class TestTwo extends TestBase {
#Test
public void testB() {
}
}
There's also a suite with the two tests:
#RunWith(Suite.class)
#Suite.SuiteClasses({ TestOne.class, TestTwo.class })
public class TestSuite {
}
In the common spring configuration file, I have a bean:
<beans ...>
<bean id="testBean" class="com.example.TestBean" />
<bean>
The problem is that, when I run the suite, the testBean gets loaded twice, once for each test class. Since it is defined in a common configuration file, is there any way to prevent it from loading multiple times?

No there is not really a chance to reuse them.
The Spring Junit Runner reuses the spring contexts over different tests, but only if there files are the same.
9.3.2.1 Context management and caching
...
Test classes provide an array containing the resource locations of XML configuration metadata - typically in the classpath - that is used to configure the application. These locations are the same as or similar to the list of configuration locations specified in web.xml or other deployment configuration files.
If this is very important for you and you are willing to spend time on it, then you can try to implement something that bases on the fact that application context can have a parent (like the two contexts in web applications, one defined for ContextLoaderListener the other defined for DispatcherServlet)) Then it is may possible to use/reuse the parent context in different test method dependend child contexts.
#See Spring Reference: Chapter 9.3.5.2 Context management and caching

Related

How to restrict inner test #Configuration class to work only for this class?

Base test class for integration testing imports base configuration with component scan that includes almost all packages. In one test class I want to override some beans with Mocs, but this inner configuration is scanned and overrides beans for all tests. Is there some way to avoid this?
I've found the way I like mocking beans with by essentially having a separate MockObjectsConfig class with the mock objects I want using the standard Spring Context Configuration, and then import it alongside my real test config. You can also annotate your mock bean with #Profile and test with #ActiveProfiles if you need to prevent a conflict there.
#Configuration
#Profile("!test")
public class MyRealConfigClass {
#Bean
public YetAnotherClass yetAnotherClass() {
return new YetAnotherClass();
}
}
#Configuration
#Profile("test")
public class MockObjectsConfig {
#Bean
public YetAnotherClass yetAnotherClass() {
Mockito.mock(YetAnotherClass.class); // and add any thenReturns, answers, etc. here
}
}
Then include it in your test like so:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = { MyRealConfigClass.class, MockObjectsConfig.class)
#ActiveProfiles({"test"})
public class MyJunitTest {
#Autowired
private RestController restController;
}
Then your mock bean will be used and not the real one from the production config.

How to manage spring hierarchy during unit/integration tests?

I am working on a Spring-enabled embedded Tomcat application using annotation-based configuration. The application uses Spring MVC Controllers for its REST endpoints. As a separation of concerns, and to avoid having duplicate beans in separate contexts, the parent context contains all beans that are not REST endpoints, and the Spring Web MVC context contains all beans that are REST endpoints.
I want to write new and refactor old integration tests for these endpoints that are representative of the structure of the app. There are existing test classes like so:
import com.stuff.web.MyEndpoint;
#Configuration
#ComponentScan(basePackages = {"com.stuff"})
public class SpringConfig { ... }
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {SpringConfig.class})
public class TestMyEndpoint {
#Autowired
private MyEndpoint myEndpoint;
private MockMvc mockMvc;
#Before
public void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(myEndpoint)
.build();
}
#Test
public void testMyEndpoint() throws Exception {
mockMvc.perform(get("/myendpoint")
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isOk())
.andReturn();
}
}
The problem is that the context that I am using for this test now has every bean loaded, whereas I would like to ensure that there are not non-REST beans loaded that call into RestController beans during the execution of the tests.
Adding something like
#Configuration
#ComponentScan(basePackages = {"com.stuff"},
excludeFilters = {
#Filter(type = FilterType.REGEX, pattern = "com.stuff.web.*")})
public class SpringConfig { ... }
Would ensure the kind of separation I'm going for, but then I don't have access to the com.stuff.web.MyEndpoint class that I'm trying to test.
Am I missing something easy? Let me know if I'm explaining the situation clearly.
The kind of separation you're describing (mvc vs non-mvc) made sense 10 years ago, not anymore. Separate your code by functionality/design patterns (web/service/repository etc), and have #Configuration classes specific to that layer. The Spring stereotype annotations are good enough hint how your app should be broken up. Then, put your tests in the same package as your target code, and mock/override any dependencies.
It doesn't appear you're using Spring Boot (you really should) but they have a great section in the docs for testing "slices" of your application.
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-tests

What is the preferred way to configure a Spring Boot Test application?

I have configured my application using #Configuration annotated classes in the config package:
main
java
com.ourcompany
config
PersistenceConfig
JacksonConfig
persistence
...
Application
test
java
com.ourcompany
persistence
PersistenceTest
The configuration gets picked up by the Application class without a problem:
#SpringBootApplication
public class Application {
public static void main( String[] args ) {
SpringApplication.run( Application.class, args );
}
}
However, the Test class:
#RunWith(SpringRunner.class)
#DataMongoTest
public class PersistenceTest {
...
}
does not pick up the configuration, unless I specify the name of the configuration class in the annotation:
#SpringBootTest(classes = PersistenceConfig.class)
I find this quite unintuitive, since I have to explicitly list all my config classes.
What is the idiomatic way to share the configuration in Spring Boot?
EDIT
In the end the problem was with including both #DataMongoTest and #SpringBootTest at the same time. Removing the #DataMongoTest and annotating the test class as:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
public class PersistenceTest {
...
}
Solved the problem. However, the question as to what is the best practice remains.

How to test Classes with #ConfigurationProperties and #Autowired

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.
You need to annotate your TestConfiguraion 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 you SettingsTest class:
#TestPropertySource(locations = "/SettingsTest.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfiguration.class)
public class SettingsTest {
...
A couple points:
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.
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.
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.
you can actually just add #EnableConfigurationProperties to your #SpringBootTest directly.
eg:
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestConfiguration.class)
#EnableConfigurationProperties
...
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:
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 {
}
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!
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());

Junit Suites with Spring autowired elements

I have a collection of Junit test classes with an autowired element (Spring). If I execute each Junit test class separately, everything works ok.
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class TestClass {
#Autowired
MyController control;
#Test
public void geolocTest() throws Exception {
...
}
I want to create a "Test Suite", but if I execute the Test Suite, it looks like the autowired elements are "null", so every test fails.
#RunWith(Suite.class)
#Suite.SuiteClasses({
TestClass.class
})
public class TestSuit extends TestCase {
...
}
What can I do? Thanks
NOTE:
What I want is to execute a code before all test classes, and another code after all test classes. I think I need a Suite for this...
As mentioned, you should use:
#RunWith(SpringJUnit4ClassRunner.class)
You can use #Before, #After, #BeforeClass, #AfterClass.
First two will be executed before and after each test case in a given Test class.
Last two will be executed only once per given Test class.
Your tests should be atomic as much as possible, so if you have all Test cases mutually dependent on same data or order of execution, try to rewrite this first.
If you wish to share some application properties, the good thing, for auto wiring between all test cases, such as the urls to mocks, take a look at the Spring profiles.
You can then have production specific and test specific properties which will be injected based on the current profile.
Take a look at the example and official docs:
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
http://www.mkyong.com/spring/spring-profiles-example/
#Configuration
#ComponentScan
#Profile("test")
#PropertySource(value = "classpath:/yourApp.properties")
public class TestConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
You can then injected shared properties from the file in your test classes like this:
#Value("${mock.url}")
private String mockUrl;
Your missing at least the following
#RunWith(SpringJUnit4ClassRunner.class)
on your TestClass.

Categories