I have wrote following test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:META-INF/dataContext.xml"},classes = Configiuration.class)
#ActiveProfiles("test")
public class CityDaoImplTest {
....
}
I need to use configuration from xml file and from java class bur when I invoke
mvn test I seee following in console:
Tests in error:
initializationError(***.CityDaoImplTest): Cannot process locations AND classes for context configuration [ContextConfigurationAttributes#5bb21b69 declaringClass = '***.CityDaoImplTest', classes = '{***.Configiuration}', locations = '{classpath:META-INF/dataContext.xml}', inheritLocations = true, initializers = '{}', inheritInitializers = true, name = [null], contextLoaderClass = 'org.springframework.test.context.ContextLoader']; configure one or the other, but not both.
How to fix it without rewriting configuration?
From the Spring Docs:
Prior to Spring 3.1, only path-based resource locations were supported. As of Spring 3.1, context loaders may choose to support either path-based or class-based resources. As of Spring 4.0.4, context loaders may choose to support path-based and class-based resources simultaneously.
However, with spring-test there is a small caveat. It uses the SmartContextLoader which is based on AbstractDelegatingSmartContextLoader and unfortunately it is not so smart ;)
#Override
public void processContextConfiguration(
final ContextConfigurationAttributes configAttributes) {
Assert.notNull(configAttributes, "configAttributes must not be null");
Assert.isTrue(!(configAttributes.hasLocations() && configAttributes.hasClasses()), String.format(
"Cannot process locations AND classes for context "
+ "configuration %s; configure one or the other, but not both.", configAttributes));
As shown in the code, locations and classes can not both be set.
So, how to fix this? Well, one solution is to add an extra config class such as the following:
#Configuration
#ImportResource("classpath:META-INF/dataContext.xml")
class TestConfig {
}
And, in your test code use the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Configuration.class, TestConfig.class})
#ActiveProfiles("test")
public class CityDaoImplTest { ... }
Technically, this is rewriting the configuration but you do not have to alter your existing config, just add a new #Configuration class (and that class can even be in the same file as your test case).
Even if is to late for you I will post my answer just to help the other ones which will read this.
Another solution is to declare your Configuration class as bean in dataContext.xml.
All you need to do is:
<bean class="com.packageWhereConfigClassIsPresent.Configuration"/>
Hope it will help someone ;)
Related
I have this JUnit 5 test:
#SpringBootTest
public class MyTest {
...
}
The application default configuration loads many #Component's #Service's and #Configuration's.
For some tests, I would like to tell the Spring application running in this JUnit test context to not scan all the components and filter out some heavy loading or verbose/spammy components that might complain while running in a (mocked) environment where not all the requirements are met.
I tried to provide the #ComponentScan annotation to MyTest class in the form below. I added on purpose multiple filters and multiple types being filtered with the hope that I see less beans being loaded/registered in the application context:
#ComponentScan(
useDefaultFilters = false,
excludeFilters = {
#Filter(type = FilterType.ANNOTATION, classes = {
org.springframework.context.annotation.Configuration.class,
org.springframework.stereotype.Service.class,
org.springframework.stereotype.Component.class
}),
#Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
MyRabbitMqListener.class
})
}
)
#SpringBootTest
public class MyTest {
#Autowired
private ApplicationContext context;
#Test
public void expectingLessRegisteredBeans() {
List<String> beans = Arrays.stream(context.getBeanDefinitionNames())
// ...
.collect(Collectors.toList());
assertEquals(beans.size(), ...);
}
}
Regardless what I provide to the #CompoenntScan, I don't manage to control the amount or what beans are being scanned/registered in the JUnit 5 Spring test context.
But I still want to use the #SpringBootTest in order to get most of my application's configuration but exclude some parts of it (because they are slow, or spammy in the logs, or just throwing errors). For example, an application that receives event from inputs like Kafka, RabbitMQ, or REST, processes them and saves them to the persistence layer. I want to test the processing and persistence integration. I throw a bunch of test events to the processor #Service and then expect to see the results in the DB via the #Repository.
What would be my alternatives:
provide #SpringBootTest a whitelist of classes I want to load (not elegant, tedious)
define several Spring profiles, put condition annotations on component and activate/deactivate only the ones I need in the test code, also use different profile application-*.properties to mute some logging (the non-test code needs to be polluted with this test feature, and tedious creating multiple profiles)
other ways to build the application context from scratch (while I actually want is to use my application configuration, except some slices which are not relevant for certain tests)
I rely on #SpringBootTest heavily when testing application configuration. App properties can be complex, having default values and non-trivial validations. For example:
prop:
ports: 1205,2303,4039
fqdn: ${server.host}:${ports[0]}/${path}
#Configuration
SomeConfigClass{
#Value{"${ports}:{#collections.emptyList}"}
List<Integer> ports;
...
}
When testing such apps, I bring up a full or partial application context without mocks, as there is complexity in the context and validation itself - mocks don't capture this. Unfortunately, there are two limitations I keep finding with this pattern:
How can we test that bad configurations fail to load?
Imagine testing that the port in invalid because it is not on the restricted range of 500 - 1500.
#SpringBootTest(
classes = {SomeConfigClass.class},
properties = "port=9000"
)
public class BadConfigTest{
#Test(expected = ApplicationContextFailedException.class)
public void WHEN_port_9000_THEN_appcontext_fails_to_load() {}
}
Since the test framework loads after the application context, there appears to be no way to test that an app context fails to load. For now I actually write the tests, manually confirm they fail, and then annotation with #Ignored so they are not lost.
How to change properties at the test method, rather than class, level?
#SpringBootTest is a class annotation, meaning application properties are bound at the test-class level. This results in needing a test class for many sets of properties and bloats the test suite. For example, I'll end up with test classes like:
ConfigPropertiesAllValidTest
ConfigPropertiesNoneSetTest
ConfigPropertiesSomeValidSomeNotTest
Where each of these only has one or two test cases. Preferably, there'd be a single ConfigPropertiesTest class with different props for each test. Is this possible?
Again - I want to avoid mocks as they don't capture the non-trivial context autoconfiguration performed by Spring at runtime.
We ended up using the ApplicationContextRunner described in this document:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-test-autoconfig
You can use the #ActiveProfiles annotation along with the #SpringBootTest annotation to load properties for different profiles. This is class level, so will only help for case 1 of your question.
#SpringBootTest(classes = {SomeConfigClass.class})
#ActiveProfiles("badconfigtest")
public class BadConfigTest{
...
}
Then have an application-badconfigtest.properties with your bad config.
I don't think you'll find a way of changing properties between test methods in the same class. You can use #DirtiesContext to reload the application context, but I've not seen a way to use different property files. I guess you could inject the values into the config classes that have already loaded the properties.
I have a test clas with
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class Foo{
...
}
which should start up a regular application context as defined by:
#SpringBootApplication(scanBasePackages = {"de.foo", "de.bar"})
public class Application {
...
}
This works as expected. Further I have an application.yml which gets loaded in both cases but when running the test, the property for JMX (spring.jmx.enabled) does not get loaded or it does not get used.
I tried different property files (application.yml, application-test.yml) but the only thing what works is setting the property via
#TestPropertySource(properties = "spring.jmx.enabled:true")
The property defaults to true in a regular application context.
Several questions:
Why is the default different in a test class?
Why does the property not get loaded or recognized, when loading it from an application.yml (the rest of the yml works, so it does get loaded).
This seems to be a known behavior, as seen in this comment in Spring Boot Sample Data Tests. Is there any documentation I missed about this behavior?
I've recently encountered the same situation myself, and have opened spring-projects/spring-boot#13008 to document this behavior. As a result, the following additions to the reference manual will be added in the upcoming 1.5.13.RELEASE and 2.0.2.RELEASE:
As the test context framework caches context, JMX is disabled by default to prevent identical components to register on the same domain. If such test needs access to an MBeanServer, consider marking it dirty as well:
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "spring.jmx.enabled=true")
#DirtiesContext
public class SampleJmxTests {
#Autowired
private MBeanServer mBeanServer;
#Test
public void exampleTest() {
// ...
}
}
On a project I'm working on we have some old dependencies that define their own spring beans but need to be initialized from the main application. These beans are all constructed using spring profiles, i.e. "default" for production code and "test" for test code. We want to move away from using spring profiles, instead simply using #import to explicitly wire up our context.
The idea is to encapsulate all these old dependencies so that no other components need to care about spring profiles. Thus, from a test`s point of view, the application context setup can be described as follows:
#ContextConfiguration(classes = {TestContext.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {
//tests
}
TestContext further directs to two classes, one of which encapsulates the old dependencies:
#Configuration
#Import(value = {OldComponents.class, NewComponents.class})
public class TestContext {
//common spring context
}
To encapsulate the old components` need for profiles, the OldComponents.class looks as follows:
#Configuration
#Import(value = {OldContext1.class, OldContext2.class})
public class OldComponents {
static {
System.setProperty("spring.profiles.active", "test");
}
}
The problem here is that the static block does not appear to be executed in time. When running mvn clean install, the test gets an IllegalStateException because the ApplicationContext could not be loaded. I have verified that the static block gets executed, but it would appear that OldContext1 and OldContext2 (which are profile dependent) are already loaded at this time, which means it is too late.
The frustrating thing is that IntelliJ runs the tests just fine this way. Maven, however, does not. Is there a way to force these profiles while keeping it encapsulated? I've tried creating an intermediary context class, but it didn't solve the problem.
If we use the annotation #ActiveProfiles on the test class, it runs just fine but this kind of defeats the purpose. Naturally, we want to achieve the same in production and this means that if we cannot encapsulate the need for profiles, it needs to be configured in the web.xml.
If your configuration classes inherits of AbstractApplicationContext you can call:
getEnvironment().setActiveProfiles("your_profile");
For example:
public class TestContext extends AnnotationConfigWebApplicationContext {
public TestContext () {
getEnvironment().setActiveProfiles("test");
refresh();
}
}
Hope it helps.
It definietly seems that OldContext1 and OldContext2 are being class-loaded and initialized before the static block in OldComponents is executed.
Whilst I can't explain why there is a difference between your IDE and Maven (to do so would require some in-depth knowledge of some, if not all all, of : spring 3.x context initialization, maven surefire plugin, SpringJunit4ClassRunner and the internal IntelliJ test runner), can I recommend to try this?
#Configuration
#Import(value = {UseTestProfile.class, OldContext1.class, OldContext2.class})
public class OldComponents {
// moved the System.setProperty call to UseTestProfile.class
}
and
#Configuration
public class UseTestProfile {
static {
System.setProperty("spring.profiles.active", "test");
}
}
If I am understanding your problem correctly, class UseTestProfile should be loaded first (you might want to investigate a way to guarantee this?) and the other two classes in the import list should have the system setting they need to initialize properly.
Hope this helps...
You need make sure, environment takes effect at first.This is how I do:
#Component
public class ScheduledIni {
#Autowired
private Environment env;
#PostConstruct
public void inilizetion() {
String mechineName = env.getProperty("MACHINE_NAME");
if ("test".equals(mechineName) || "production".equals(mechineName) {
System.setProperty("spring.profiles.default", "Scheduled");
System.setProperty("spring.profiles.active", "Scheduled");
}
}
}
In scheduler add annotation Prodile and DependsOn to make it work.
#DependsOn("scheduledIni")
#Profile(value = { "Scheduled" })
#Component
Use #profile annotation in the class to load the configuration like below
#Configuration
#Profile("test")
public class UseTestProfile {
}
and set the value for the property spring.profiles.active either in property file or as a runtime argument
I have some tests (I didn't write them, I am maintaining them) that use the spring ContextConfiguration annotation to supply application contexts:
#ContextConfiguration(locations = { "testCustomContext.xml" })
public class MyTest {
}
Anyway, a couple of questions. I am not so familiar with spring custom context locations that don't specify file:/ or classpath:/. What does it mean? There are a lot of resources on this test class path with that name. Are they all loaded? If not, how does Spring know which to load?
Second, is there a way to programmatically access a spring context that has been wired in this way?
I.e. is there a static Spring Class or ThreadLocal variable that would give me access to the current context?
Thanks in advance for the help.
You can gain access to application context by just autowiring it inside test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class MyTest {
#Autowired
private ApplicationContext applicationContext;
// class body...
}
As for your second question:
I am not so familiar with spring custom context locations that don't
specify file:/ or classpath:/. What does it mean? There are a lot of
resources on this test class path with that name. Are they all loaded?
If not, how does Spring know which to load?
From Java Docs:
A plain path — for example, "context.xml" — will be treated as a
classpath resource that is relative to the package in which the
specified class is defined. A path starting with a slash is treated
as an absolute classpath location, for example:
"/org/springframework/whatever/foo.xml". A path which references a
URL (e.g., a path prefixed with classpath:, file:, http:, etc.) will
be added to the results unchanged.
You can learn about Spring Resources in docs: http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/resources.html
Also JavaDocs for #ContextConfiguration can give you more knowledge.
I encourage you to study Spring Docs.