Spring boot - How to set an Application Property #Value only for a particular class - spring-boot

We use an application property to enable/disable a functionality.
#Component
#ConditionalOnProperty(
value = "spring.redis.cache.producer.enabled",
havingValue = "true")
public class MyEntityListener {
// Omitted
}
But we don't want to activate this in most part of our test base, so we are adding a TestPropertySource and try to get rid of it after class using #DirtiesContext
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#TestPropertySource(properties = {"spring.redis.cache.producer.enabled=true", "spring.redis.cache.listener.enabled=true"})
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#DirtiesContext
public class AimaCacheControllerTest {
// Omitted
}
But the Spring Application context is not necessarily restarted after this class, breaking many consequent tests. Since mvn surefire plugin is running the tests in random order, it is not an option to do #DirtiesContext(classMode=BEFORE_CLASS) in the following tests - as we don't know which one that will be.
So the main issue here is to be able to inject an application property and make sure it is reset after the tests are over. How would we achieve that?
I feel like this should be quite common case but I couldn't find any working solution yet.

Related

How to load mongo repository to spring test application context?

I have to implement some test for some spring application. I am using #SpringBootTest annotation in my test:
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest(){
//some tests...
}
It works fine, but i do not want to load all application context and and limit it by adding one or more nessecary configuration class. I done it with #ContextHierarchy:
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ContextHierarchy(ContextConfiguration(classes = [SomeCofigClass1::class, SomeConfigClass2::class]))
class MyTest(){
//some tests...
}
//for example
class SomeCofigClass1(){
#Bean
fun(someMongoRepository: SomeMongoRepository){ \\<-- Problem is here
return SomeService(someMongoRepository)
}
}
/**
* My repository.
*/
interface SomeMongoRepository : MongoRepository<Job, String> {}
Because of context is partially loaded i got a error:
No qualifying bean of type 'SomeMongoRepository' available:....
How can i load repository in test application context?
I already tried:
1) Added #AutoConfigureDataMongo. I got error java.lang.IllegalStateException: Unable to retrieve #EnableAutoConfiguration base packages
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ContextHierarchy(ContextConfiguration(classes = [SomeCofigClass1::class, SomeConfigClass2::class]))
#AutoConfigureDataMongo
2) Replaced #SpringBootTest by #DataMongoTest. I got error Unable to retrieve #EnableAutoConfiguration base packages
#DataMongoTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ContextHierarchy(ContextConfiguration(classes = [SomeCofigClass1::class, SomeConfigClass2::class]))
3) With #DataMongoTest replaced #ContextHierarchy by #Import. With #Import annotation it loads all application context. This is not suit for me.
#DataMongoTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#Import(SomeCofigClass1::class, SomeConfigClass2::class)
Add #RunWith(SpringRunner.class) to load Spring's ApplicationContext during the test.
Furthermore, classes can be selected by using the #SpringBootTest#classes attribute (or #ContextConfiguration):
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {SomeCofigClass1.class, SomeConfigClass2.class})
If your test makes use of inner Configuration classes, be sure to make them static (and add #Configuration). From the documentation:
If you omit the classes attribute from the #ContextConfiguration annotation, the TestContext framework tries to detect the presence of
default configuration classes. Specifically,
AnnotationConfigContextLoader and AnnotationConfigWebContextLoader
detect all static nested classes of the test class that meet the
requirements for configuration class implementations, as specified in
the #Configuration javadoc.

Is it possible to activate a spring profile in a WebMvcTest

Given a test class like:
#WebMvcTest
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "spring.profiles.active=test")
public class MyControllerTest {
... some tests
}
I get the error:
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class [com.example.MyControllerTest]: [#org.springframework.test.context.BootstrapWith(value=class org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper), #org.springframework.test.context.BootstrapWith(value=class org.springframework.boot.test.context.SpringBootTestContextBootstrapper)]
The desired goal is that I'm just running a controller test and thus for test performance reasons do not want to set up the whole context - I just want the "web layer".
I can remove the #SpringBootTest(properties = "spring.profiles.active=test") line - however, now I've not activated the test profile, which may customise the web context in some way via properties, e.g. jackson customisations that will no longer be applied. Is there a way to get a "web layer" only test and still activate a spring profile?
My environment is java version "10.0.2" 2018-07-17, spring boot 1.5.16.RELEASE
To set active profile, you can use #ActiveProfiles, like this
#WebMvcTest
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
public class MyControllerTest {
then you can you application-test yml or properties in test resources.
Additionally, if you are getting active profile in programmatic way like this:
String environment = System.getProperty("spring.profiles.active");
Then you can set this property in the static block in your test class:
#WebMvcTest
public class MyControllerTest {
static {
System.setProperty("spring.profiles.active", "foo");
}
}

How does the JUnit plugin in Eclipse get the list of test methods in my class?

I am running my test in a Spring Boot application that contains several usual annotations:
#RunWith(SpringRunner.class)
#ActiveProfiles(profiles = { "DEV" })
#TestConfiguration
#SpringBootTest(classes = { Application.class }, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class IntegrationTestBase {
..
}
and my other tests simply extend this class and I have a bunch of methods in there annotated with #Test. When I launch this I see that first Spring is booted (at this time the JUnit view in Eclipse does not show test methods yet) and when that process finishes my test methods appear there magically and the execution starts.
I am wondering what works under the hood that makes this work.

using ConfigFileApplicationContextInitializer does not resolve default values for #Value

When I am using:
#ContextConfiguration(classes = { DmiVehicleRTExportConfig.class },
initializers = ConfigFileApplicationContextInitializer.class)
my integration tests are failing as properties such as:
${oauth2client.prematureTimeout:600}
are not getting default value of 600.
This is resolved through using: SpringApplicationConfiguration but I want to use ConfigFileApplicationContextInitializer.class instead of SpringApplicationConfiguration.
Thanks.
I had the same issue. PropertySourcesPlaceholderConfigurer.class needs to be added:
#ContextConfiguration(classes = { DmiVehicleRTExportConfig.class,
PropertySourcesPlaceholderConfigurer.class },
initializers = ConfigFileApplicationContextInitializer.class)
Spring team has just added a note about that:
Using ConfigFileApplicationContextInitializer alone won’t provide support for #Value("${…​}") injection. Its only job is to ensure that application.properties files are loaded into Spring’s Environment. For #Value support you need to either additionally configure a PropertySourcesPlaceholderConfigurer or use #SpringBootTest where one will be auto-configured for you.
I had this problem in JUnit tests also when using
#SpringApplicationConfiguration
I solve this by adding a class with a 'static void main()' method to the configuration classes :
#Configuration
public class SpringEmptyApplication{
public static void main(String[] args) {
SpringApplication.run(SpringBootEmptyApplication.class);
}
}
I put this class as first to the list of configuration classes like
#SpringApplicationConfiguration(classes = {
SpringBootEmptyApplication.class,
DmiVehicleRTExportConfig.class
})
This works perfect for me.
If you want to avoid
#SpringApplicationConfiguration
you can try
#ContextConfiguration(loader = SpringApplicationContextLoader.class ...
This is how #SpringApplicationConfiguration itself is annotated, may be this loader can fix the problem, did not try...

Prevent Application / CommandLineRunner classes from executing during JUnit testing

If in your TestCase class there is this annotations:
#SpringApplicationConfiguration(classes = {Application.class})
this will cause the Application.class, implementing the CommandLineRunner interface, to run the required method
public void run(String... args) throws Exception
I still think this is, mostly, a not wanted behaviour, since in your test environment you may not want to launch the entire application.
I have in mind two solution to circumvent this problem:
to remove the CommandLineRunner interface from my Application class
to have a different context for testing
Both this solution requires lot of coding.
Do you have a more convenient solution?
Jan's solution can be achieved easier.
In your test class, activate the "test" profile:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
public class MyFancyTest {}
In your CommandLineRunner set the profile to NOT test:
#Component
#Profile("!test")
public class JobCommandLineRunner implements CommandLineRunner {}
Then you don't have to manually set the profile in the Application.
As mentioned in the spring documentation you can use #ContextConfiguration with a special initializer:
ConfigDataApplicationContextInitializer is an ApplicationContextInitializer that you can apply to your tests to load Spring Boot application.properties files. You can use it when you do not need the full set of features provided by #SpringBootTest
In this example anyComponent is initialized and properties are injected, but run(args) methods won't be executed. (Application.class is my main spring entry point)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class,
initializers = ConfigDataApplicationContextInitializer.class)
public class ExtractorTest {
#Autowired
AnyComponent anyComponent;
#Test
public void testAnyComponent() {
anyComponent.anyMethod(anyArgument);
}
}
You can define a test configuration in the same package as your application that looks exactly the same, except that it excludes beans implementing CommandLineRunner. The key here is #ComponentScan.excludeFilters:
#Configuration
#ComponentScan(excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CommandLineRunner.class))
#EnableAutoConfiguration
public class TestApplicationConfiguration {
}
Then, just replace the configuration on your test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = TestApplicationConfiguration.class)
public class SomeApplicationTest {
...
}
No CommandLineRunner will be executed now, because they are not part of the configuration.
I'm a bit late to the party, but a reasonable approach is to mark the bean with #ConditionalOnProperty, e.g.
#ConditionalOnProperty(prefix = "job.autorun", name = "enabled", havingValue = "true", matchIfMissing = true)
public CommandLineRunner myRunner() {...}
The following annotation will then disable it in tests:
#SpringBootTest(properties = {"job.autorun.enabled=false"})
If you have a mocking framework installed (e.g. MockMVC) you can create a mock instance of the CommandLineRunner implementation, more or less disabling it:
#MockBean
private TextProcessor myProcessor;
Previous answers didn't work wor me. I ended up using different profiles - example for the init method in Spring Boot:
SpringApplication app = new SpringApplication(AppConfig.class);
app.setAdditionalProfiles("production");
app.run(args);
This is not executed during the tests so we're safe here.
All tests have their own profile "test" (which is useful in many other ways, too):
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
public class MyFancyTest {}
The command-line runner is annotated with the "production" profile so the tests ignore it:
#Component
#Profile("production")
public class JobCommandLineRunner implements CommandLineRunner {}
I solve this by not implementing CommandLineRunner. Just get a bean from the context, and call a method on it, passing argv. That way you will get the same result, and the application won't start automatically when running the tests.

Resources