We’ve configured multiple Spring Boot profiles in our application and data source changes according to the profile activated.
We’ve separate data source for Junit Tests. Now, we want to use this data source for JUnit Tests irrespective of the profile activated.
We were able to achieve this using #TestPropertySource in every test class but we need this configuration in one place in pom.xml. I’m aware of maven-surefire-plugin which is used to execute Junit Tests but not sure how we can configure particular data source there.
Is there any way to achieve this?
Any help would be much appreciated.
One simple way of doing this is to create profile-dependable configuratons as wrappers over normal configurations, like this:
Profile 1:
#Profile("ds1")
#Import(Ds1Configuration.class)
public class Ds1ProfileConfiguration {}
Profile 2:
#Profile("ds2")
#Import(Ds2Configuration.class)
public class Ds2ProfileConfiguration {}
where the imported configurations contain actual bean definitions:
public class Ds1Configuration {
#Bean
public DataSource dataSource(){...}
}
and
public class Ds2Configuration {
#Bean
public DataSource dataSource(){...}
}
This will separate your profiles and configurations, so you will be able to use these in your tests:
#SpringBootTest(classes = Ds2Configuration.class)
Related
How we can programmatically configure Spring Boot to define new values to the spring.config.name and spring.config.location properties when running JUnit tests?
For example, if we would like to define these properties when running the application itself we could use something like (in Kotlin):
fun main(args: Array<String>) {
// SpringApplication.run(Application::class.java, *args)
val applicationContext = SpringApplicationBuilder(Application::class.java)
.properties(
"""spring.config.name:
${getSpringConfigNames()}
""",
"""spring.config.location:
${getSpringConfigLocationPaths()}
"""
)
.build()
.run(*args)
// val environment = applicationContext.getEnvironment()
}
But I wasn't able to find a way to configure this to use in the JUnit tests.
Edit
There is a complication here because of an spring boot limitation.
I would like to use an entire folder and its subfolders as valid locations to search for configuration files (so, for example, we could have folders for specific environments, databases, third-parties, and so on).
When running the application this was possible creating a method, in this case getSpringConfigLocationPaths(). And this method create a comma separated list with all folder inside the "main" folder.
For example, for the main folder src/main/resources/configuration it will output:
src/main/resources/configuration,
src/main/resources/configuration/environments,
src/main/resources/configuration/environments/development,
src/main/resources/configuration/environments/staging,
src/main/resources/configuration/environments/testing,
src/main/resources/configuration/environments/production,
src/main/resources/configuration/environments/common
How could we solve this situation when using JUnit tests and Spring Boot?
Unfortunately Spring Boot doesn't allow something like src/main/resources/configuration/**/*.
Because we have organized the system with several properties files on different subfolders we need to find a way to dinamically consider them.
I am using latest Spring Boot 2.2.0 and from my experience both #TestPropertySource and #SpringBootTest annotations can do the job because they have properties attribute.
So, you can do something like this:
#TestPropertySource(properties = ["spring.config.location=classpath:dev/", "spring.config.name=custom-app-name"]
#TestConfiguration
class DevTestCfg {} // this will make tests to look for configs in resources/dev/custom-app-name.properties
Also notice that there is a spring.config.additional-location property if you want your properties to be loaded from multiple locations.
The only problem here is that values in properties attribute must be constant.
But you can create multiple configurations for each environment and put corresponding #Profile("envName") on each configuration class. Then run your tests with different -Dspring.profiles.active and corresponding test configuration should be automatically picked up.
The tests that run spring boot should be carefully designed,
There is a whole testing framework for spring boot tests, so obviously consider using this framework.
When it comes to configuration management, I suggest considering the following:
There are two types of tests basically:
Tests that load a concrete specific configuration (set of beans), for example if you want to test only a DAO, you load a configuration for this dao.
In this case, the configuration is something that should be "tailored" to the needs of a specific test, and no "full" configuration is required.
For example, if the microservice contains a configuration for a database (user, password, schema, etc) and for, say, messaging management, there is no need to specify a configuration of a messaging system when testing a DAO, messaging beans won't be loaded anyway.
Usually, the test of this "type" will look like this:
#SpringBootTest(classes = {RelationalDbDaoConfiguration.class})
public class MyDaoTest {
}
If you don't have a configuration for your needs you can use #MockBean to mock unnecessary beans or even create a custom configuration in src/test/java so that it will be only in test classpath. It makes sense to use #TestConfiguration but it's beyond the scope of the question.
Now in order to load the configuration for db only, the are many options, to name a few:
#ActiveProfiles("dao") on a test class + putting "application-dao.properties/yaml" into the src/test/resources or src/test/resources/config
Use #TestPropertySource(locations = "classpath:whatever.properties") on test
Create a special "DbProperties" bean and initialize it programmatically in spring, it can make sense when you know some details about the context in which the test runs only during the actual test execution (for example, if you start a database before the test and the port is created dynamically, but its really a fairly advanced setup and is beyond the scope of this question) + the data source bean can read these properties
Use #SpringBootTest's properties attribute to provide 'fine-grained' properties definitions
Kind of obvious, but I'll mention it anyway: put application.properties in src/test/resources it will override regular configurations
The second type of tests is when you load the "entire" microservice, usually, these are tests that do not have "classes" parameter in #SpringBootTest annotation
#SpringBootTest // note, no actual configurations specified
public class MyMicroserviceTest {
...
}
Now, this definitely requires to specify a whole set of configurations, although the techniques for actually specifying these configurations are still applicable (just the content of configuration files will be different).
I do not suggest the usage of spring.config.location during the test, because this means that the test depends on some external resource, which makes the whole setup even more complicated.
If it's XML driven configuration,
#ContextConfiguration(locations = "/app-context.xml")
If it's annotation driven by configuration classes,
#ContextConfiguration(classes = {AppCOnfig::class, AnotherCOnfig::class}
These would be defined on the class level on the unit test class you run.
Further, if you have profiles for Junit to consider,
#ActiveProfiles("myProfile") would be added to the test class.
I'm using one mapper generated with MapStruct:
#Mapper
public interface CustomerMapper {
Customer mapBankCustomerToCustomer(BankCustomerData bankCustomer);
}
The default component model is spring (set in pom.xml)
<compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg>
I have a service in which I inject the customer mapper and works fine when I run the application
#Autowired
private CustomerMapper customerMapper;
But when I run unit tests that involves #SpringBootTest
#SpringBootTest
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
public class SomeControllerTest {
#Mock
private SomeDependency someDependency;
#InjectMocks
private SomeController someController;
#Test
public void shouldDoSomething() {
...
}
}
I get an org.springframework.beans.factory.UnsatisfiedDependencyException
Unsatisfied dependency expressed through field 'customerMapper'
I followed this answer and my problem was solved as quickly as I pasted proposed lines in my build.gradle file
As you are running your tests via the IDE there are 2 possibilities:
Eclipse or IntelliJ is picking up the Annotation Processors, you need to set them up correctly.
Eclipse or IntelliJ does not pick up the compiler options from the maven compiler
To rule out the possibilities do the following for each:
Make sure the IDE is configured to run APT. Have a look here how you can set it up. Run a build from the IDE and check if there are generated mapper classes
If there are they are most probably generated with the default component model. To solve this you have two options:
Use #Mapper(componentModel = "spring"). I personally prefer this option as you are IDE independent. You can also use a #MapperConfig that you can apply
Configure the IDE with the annotation options. For IntelliJ add the compiler argument in Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors, there is a section called Annotation Processor Options there add mapstruct.defaultComponentModel as option name and spring as value. I am not sure how to do it for Eclipse
I am writing a library to provide some functionality that is shared between multiple different Spring Boot applications that I work with.
I would like to do something similar to the auto-configuration that is provided by the many Spring Boot starter libraries exist. That, or some other simple declarative way to integrate my library with the ApplicationContext of the apps using it.
I have found some resources explaining how auto configuration works. I can figure out the above problem.
However, I have not been able to find any good examples of how I can test as part of my library's test suite that it suitably integrates with a Spring Boot application. Ideally, I would start up a simple Spring Boot app written in the library's test directly just for the sake of testing, add the right annotation to it, and be able to assert that the correct beans are then configured.
I have tried creating a TestApplication class that does that and writing integration tests using the SpringBootTest annotation but the TestApplication was never started before my test started.
What can I do to start up a simple app like that solely for the purpose of testing my library? My tests are written with Spock and Spock-Spring in case that changes things versus other test frameworks.
I was able to make it work with the following test class:
#SpringBootTest
#ContextConfiguration(classes = TestApplication)
class DummyIntegrationSpec extends Specification {
#Autowired
DummyService dummyService
void 'dummy service should exist'() {
expect:
dummyService.getMessage() == DummyConfiguration.MESSAGE
}
}
and this TestApplication class at src/test/groovy/com/example/project/TestApplication.groovy
#SpringBootApplication(scanBasePackages = 'com.example.project.config')
#EnableAutoConfiguration
class TestApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(TestApplication)
}
static void main(String[] args) {
SpringApplication.run(TestApplication, args)
}
}
The two key changes I had to make in order for the TestApplication to start and load the correct context when I moved my TestApplication class from src/main to src/test were:
the TestApplication class needed to be added to the ContextConfiguration annotation
the package that my library's Java config files live in needed to be added to the SpringBootApplication scanBasePackages field
The library auto-configuration does follow a similar structure to the one mentioned in the link tom provided.
Your auto-configuration should be automatically picked while your main spring application/test is starting and all beans will be registered in your context. They will be available for auto-wiring and follow your conditions and init order.
As a summary, make sure you have an auto-configuration annotated by #Configuration class with an #Import that imports your #Configuration annotated configuration classes (inside of them you define beans with methods annotated with #Bean). Also make sure you created a spring.factories file that include your auto-configuration class and that you removed the spring boot maven plugin (for the packaging to be right).
Also, make sure your auto-configuration project is NOT annotated by things like #SpringBootApplication, #EnableAutoConfiguration, #ComponentScan or other spring boot annotations that need to be only in the main spring boot projects (There should be one of them in each stack).
Please also see the article below:
Spring boot is based on a lot of pre-made auto-configuration parent projects. You should already be familiar with spring boot starter projects.
You can easily create your own starter project by doing the following easy steps:
Create some #Configuration classes to define default beans. You should use external properties as much as possible to allow customization and try to use auto-configuration helper annotations like #AutoConfigureBefore, #AutoConfigureAfter, #ConditionalOnBean, #ConditionalOnMissingBean etc. You can find more detailed information on each annotation in the official documentation Condition annotations
Place an auto-configuration file/files that aggregates all of the #Configuration classes.
Create a file named spring.factories and place it in src/main/resources/META-INF.
In spring.factories, set org.springframework.boot.autoconfigure.EnableAutoConfiguration property with comma separated values of your #Configuration classes:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
Using this method you can create your own auto-configuration classes that will be picked by spring-boot. Spring-boot automatically scan all maven/gradle dependencies for a spring.factories file, if it finds one, it adds all #Configuration classes specified in it to its auto-configuration process.
Make sure your auto-configuration starter project does not contain spring boot maven plugin because it will package the project as an executable JAR and won't be loaded by the classpath as intended - spring boot will not be able to find your spring.factories and won't load your configuration
I am using #Profile Spring annotations to choose between embedded, standalone and container managed data sources. In order to choose 'embedded' my integration tests are annotated to activate the appropriate profile:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes={TestConfigWrapper.class})
#ActiveProfiles({"EMBEDDED_DB"})
public class SomeIntegrationTest {
The problem is that I would like to move '#ActiveProfiles' into TestConfigWrapper, but doing this doesn't get picked up and the application context won't load any DataSources.
This means I have to annotate every integration test with an #ActiveProfile which effectively means it becomes integration test boiler-plate and could easily hamper future refactoring.
Is there a way I can do this using java config?
Per comment from Hippooom use an abstract class to configure tests:
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={WebAppInitializer.class})
#ActiveProfiles({Profiles.EMBEDDED_DB})
public abstract class ProfiledIntegrationTest {
}
In Guice, I have a ProductionModule with my bindings. In my tests, I can load that PLUS a module that overrides a few of the production bindings with mock objects. How do I do such a thing in spring....
For example, load production-spring.xml in test files and then have the test load in test-spring.xml which would ONLY override some of the bindings in production-spring.xml
This tests the integration and make sure changes in production-spring.xml don't break things. These are more automated integration tests then testing a unit and work extremely well.
You can override beans by listing multiple xml files. Beans in the later files will override those loaded before.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
locations = {"classpath:prodDB.xml",
"classpath:applicationContext.xml",
"classpath:testDb.xml"})
public class SpringTest {
#Autowired
protected DataSource dataSource; //uses the datasource from testDb.xml
}
So in this case testDB.xml overrides the DataSource configured in prodDb.xml. This applies even if you don't use the SpringJUnit4ClassRunner as well:
new ClassPathXmlApplicationContext(new String[]
{"classpath:prodDb.xml",
"classpath:testDb.xml"});
Using a tool like Constretto you can do the same with annotated beans:
#Service
public class FooService...
#Service
#Environment("test")
public class FakeFooService ...
Now, if you run a test with the #Environment("test") annotation on the class, the FakeFooService will be used.
In test environment, you can add your Spring context xml with with overwritten beans before other declarations. And there is also external properties you can change for each environment.