I am working on custom Spring Boot starters. In a test starter what I wanted do to is to implement a composed annotation, which would add additional #Configuration classes to the ApplicationContext (and possibly use this annotation in a TestExecutionListener). ex:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#ContextConfiguration(classes = AdditionalTestConfiguration.class)
public #interface ComposedAnnotation {
}
And use that in my Spring Boot integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#WebIntegrationTest
#SpringApplicationConfiguration(Application.class)
#ComposedAnnotation
public class SomeTest {
}
No inheritance is involved. Unfortunately, it does not seem to work. I doubt it's a Spring Boot thing, rather Spring testing framework itself.
Is there any way I can achieve expected result?
You're right: this is not an issue with Spring Boot. But it's also not an issue with spring-test.
Rather, it's the intended behavior of Spring in general. For details, check out my answer to this question: #ActiveProfiles in meta annotation and on test class not working
In summary, you cannot achieve this with two #ContextConfiguration annotations declared on an individual test class (either directly or as meta-annotations).
However, I just came up with a trick that will allow you to achieve this. Specifically, you can create an ApplicationContextInitializer (ACI) that registers one or more #Configuration classes. In your composed annotation, you can then register this ACI to register the always present #Configuration classes. And when the composed annotation is actually used, it can declare additional #Configuration classes like normal.
I just submitted a working example in this commit.
Basically, the code would look something like this:
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, initializers = FooConfigInitializer.class)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ComposedContextConfiguration {
#AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
Class<?>[] value() default {};
}
public class FooConfigInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
#Override
public void initialize(GenericApplicationContext applicationContext) {
new AnnotatedBeanDefinitionReader(applicationContext).register(FooConfig.class);
}
}
And you can use it like this:
#RunWith(SpringRunner.class)
#ComposedContextConfiguration(BarConfig.class)
public class InitializerConfiguredViaMetaAnnotationTests { /* ... */ }
Your ApplicationContext will then be loaded from FooConfig and BarConfig.
The above examples obviously do not use Spring Boot, but the same principles should also be applicable to #SpringApplicationConfiguration.
Regards,
Sam (author of the Spring TestContext Framework)
Related
I have a configuration class which registers beans based on a very simple condition (checking a property value in application.properties). The configuration class and the condition are the following:
#Configuration
#Conditional(DatabaseConfigurationCondition.class)
#ComponentScan(basePackageClasses = DBConfigComponents.class)
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
}
and
public class DatabaseConfigurationCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return conditionContext.getEnvironment().getProperty("configuration.type").contains("db");
}
}
In addition of the beans registered in this configuration class I have component scan which scans for other components. When the condition is not met, I expect the beans which are defined in the configuration class not to be registered (which happens to be a case), but also I expect other classes which are annotated with #Component (or #Repository, #Service, etc.. ) and are in same folder as DBConfigComponents.class marker interface not to be registered, which does not happen. Beans which are scanned are always registered, no matter if the condition is fulfilled or not.
When I put the #Conditional(DatabaseConfigurationCondition.class) on each #Component annotated class, than it's working correctly, but I don't want to put it on each class separately.
Any suggestion?
Fortunately, I managed to fix this. The problem in my case was that I had another #ComponentScan annotation placed in other configuration class in other Maven module - not conditional on any property. The components which are in same package as DBConfigComponents marker interface were actually scanned by the other configuration class.
The way #ComponentScan works is on package level. Although, in different Maven modules, both configuration classes were in same package. #ComponentScan works perfectly fine with #Conditional. No need #Conditional to be placed on each component separately.
The best way to achieve this is not to annotate these beans using #Component / #Service and #Repository annotations. Instead you should return these as part of the configuration you have setup which would be DatabaseConfigurationLoader. See sample below.
#Configuration
#Conditional(DatabaseConfigurationCondition.class)
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
#Bean
public SomeService someService() {
return new SomeService();
}
#Bean
public SomeComponent someComponent() {
return new SomeComponent();
}
}
Note: Typically #Configuration with #Conditional are used in libraries that you want to include in your spring boot application. Such libraries should not share the same package as your spring boot application. Thus they should not be picked up by #ComponentScan annotation. Beans from libraries should not be annotated with #Component / #Service / #Repository annotations. Spring suggests using AutoConfiguration for that. See https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html & https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html
No need to implement Condition interface, you need to use '#ConditionalOnProperty' annotation:
#Configuration
#ComponentScan(basePackageClasses = DBConfigComponents.class)
#ConditionalOnProperty(name = "configuration.type", havingValue = "db")
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
}
you can use 'prefix' instead of 'havingValue' depending on your needs.
how can I override beans in Spring (Boot) Integration Tests the idiomatic way?
Up until now I had source configuration like this:
#Configuration
class ApplicationConfiguration {
#Bean
CarsRepository carsRepository() {
// return some real sql db
}
}
And tests like this:
#SpringBootTest
class ApplicationISpec extends Specification {
#Configuration
#Import(Application.class)
static class TestConfig {
#Bean
#Primary
CarsRepository testsCarsRepository() {
// return some in-memory repository
}
}
def "it can do stuff with cars"() {
// do some REST requests to application and verify it works
// there is no need to make real calls to real DB
}
}
First thing is that test bean testsCarsRepository method must differ than original one (which is not obvious, and there is no warning/error about it).
But the final question is: what is the idiomatic way of overriding beans with Spring in integration tests?
When I posted my WTF about method name on Twitter - Stephane Nicoll said the #Primary is not intended to be used for overriding beans in tests.
So what is the preferred way of that?
You can use #Profile together with #ActiveProfile annotation to separate you test and production configurations. For example change you test config to:
#SpringBootTest
#ActiveProfiles("test")
class CarsISpec extends Specification {
#Configuration
#Import(Application.class)
#Profile("test")
static class TestConfig {
#Bean
CarsRepository testsCarsRepository() {
// return some in-memory repository
}
}
}
Don't forget to mark you production configuration ApplicationConfiguration with #Profile("!test").
Also Spring Boot provides numerous tools for testing (e.g. #DataJpaTest with embedded database, #MockBean for mocking beans in context and etc.) Link to doc
My goal is to migrate a Spring Boot application previously developed with Spring Boot 1.3 to the newest Spring Boot version 1.4. The application consists of several maven modules and only one of them contains class annotated with #SpringBootApplication.
One part of migration is to use #WebMvcTest annotation to efficiently test controllers, and here I get an issue.
Consider an example application from Spring Boot github page. #WebMvcTest annotation works perfectly, because, as far as I understand (after I did several tests), there is a class in the main package annotated with #SpringBootApplication. Note that I follow the same concept as shown in the example above for my own #WebMvcTest tests.
The only difference I see that in my application, controller classes are located in a separate maven module (without #SpringBootApplication annotated class), but with #Configuration and SpringBootConfiguration configurations. If I do not annotate any class with #SpringBootApplication I always get an assertion while testing controller. My assertion is the same as when SampleTestApplication class in the example above modified to have only #EnableAutoConfiguration and #SpringBootConfiguration annotations (#SpringBootApplication is not present):
getVehicleWhenRequestingTextShouldReturnMakeAndModel(sample.test.web.UserVehicleControllerTests) Time elapsed: 0.013 sec <<< FAILURE!
java.lang.AssertionError: Status expected:<200> but was:<404>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
at sample.test.web.UserVehicleControllerTests.getVehicleWhenRequestingTextShouldReturnMakeAndModel(UserVehicleControllerTests.java:68)
How should I deal with that? Should I always have class annotated with #SpringBootApplication in order to run #WebMvcTest tests?
EDIT 1: I did a small maven project with 2 modules and a minimal configuration. It is here. Now, I get NoSuchBeanDefinitionException exception for repository defined in another module. If I configure "full" #SpringBootApplication - everything is fine.
EDIT 2: I modified small test project from EDIT 1 to give an original issue. I was playing with different annotations and added #ComponentScan on configuration class, because I suspected that beans are not registered properly. However, I expect that only #Controller bean (defined in #WebMvcTest(...class)) shall be registered based on magic behind #WebMvcTest behaviour.
EDIT 3: Spring Boot project issue.
Short answer: I believe so.
Long answer:
I believe #WebMvcTest needs to find the SpringBootApplication configuration since WebMvcTest's sole purpose is to help simplify tests (SpringBootApplication would rather try to load the whole world).
In your specific case, since you don't have any in your non-test packages, I believe it also finds SampleTestConfiguration which is annotated with #ScanPackages and somehow loads every beans.
Add the following in src/main/java/sample/test
#SpringBootApplication
public class SampleTestConfiguration {
}
And change your test to this:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private MyService ms;
#Autowired
private ApplicationContext context;
#Test
public void getDataAndExpectOkStatus() throws Exception {
given(ms.execute("1")).willReturn(false);
mvc.perform(get("/1/data").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()).andExpect(content().string("false"));
}
#Test
public void testMyControllerInAppCtx() {
assertThat(context.getBean(MyController.class), is(not(nullValue())));
}
#Test
public void testNoMyAnotherControllerInAppCtx() {
try {
context.getBean(MyAnotherController.class);
fail("Bean exists");
} catch (BeansException e) {
// ok
}
}
}
#WebMvcTest finds the SpringBootApplication, then load only a limited number of beans (see documentation):
#WebMvcTest will auto-configure the Spring MVC infrastructure and
limit scanned beans to #Controller, #ControllerAdvice, #JsonComponent,
Filter, WebMvcConfigurer and HandlerMethodArgumentResolver. Regular
#Component beans will not be scanned when using this annotation.
WebMvcTest requires SpringBootApplication: WebMvcTest inherits many AutoConfiguration, so it needs SpringBoot to load them. Then it disables many other AutoConfiguration and your Controllers become easily testable.
The whole point of using WebMvcTest is when you have a SpringBootApplication and you wish to make it simpler to test by disabling all beans except Controllers. If you don't have SpringBootApplication, then why use WebMvcTest at all?
It's an old topic, but there is a solution which wasn't mentioned here.
You can create a class annotated with SpringBootApplication just in your test sources. Then, you still have a nice, multi-module structure of your project, with just one "real" SpringBootApplication.
Yes,according to the spring boot docs
The search algorithm works up from the package that contains the test until it finds a #SpringBootApplication or #SpringBootConfiguration annotated class. As long as you’ve structure your code in a sensible way your main configuration is usually found.
But after I started using #WebMvcTest,spring boot still try to load other beans, finally TypeExcludeFilter did the trick.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = {JzYsController.class} )
public class JzYsControllerTest {
private static final String REST_V4_JZYS = "/rest/v4/JzYs";
#Autowired
private MockMvc mockMvc;
#MockBean
private JzYsService service;
#Test
public void deleteYsByMlbh() throws Exception {
Mockito.when(service.deleteYsByMlbh(Mockito.anyString())).thenReturn(Optional.of(1));
mockMvc.perform(delete(REST_V4_JZYS + "?mbbh=861FA4B0E40F5C7FECAF09C150BF3B01"))
.andExpect(status().isNoContent());
}
#SpringBootConfiguration
#ComponentScan(excludeFilters = #Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public static class config{
}
}
There is one more solution. You can not use #WebMvcTest, but configure MockMvc yourself through the builder
class TestControllerTest {
private MockMvc mvc;
#BeforeEach
public void setup() {
mvc = MockMvcBuilders.standaloneSetup(new TestController())
.build();
}
#Test
void test() throws Exception {
// When
var res = mvc.perform(MockMvcRequestBuilders.get("/test/test"));
// Then
res.andExpect(status().isOk());
}
}
But this solution may entail a number of other problems, such as problems with configurations, environment property injections, etc.
What is the difference between below two ways of loading configuration.
Two independent config classes are loaded via #ContextConfiguration in test class.
Import one config into another config and load the single into #ContextConfiguration in test class.
I thought both pushes the configuration into the common pool. But i see a difference. I have two global interceptor one is in java.config and another in xml.config. If i follow 2nd approach above, both interceptors are loading.
But if i follow 1st approach, only one interceptor is loading based on whether i'm invoking xml based gateway or java based gateway.
https://github.com/manojp1988/Learning/tree/JavaDSL/Sample1
#RunWith(SpringJUnit4ClassRunner.class)
#ContextHierarchy({
#ContextConfiguration(locations = {"/applicationContext.xml"}),
#ContextConfiguration(classes = SpringConfiguration.class),
})
public class SampleTest {}
Updated:
#Bean
#GlobalChannelInterceptor(patterns = "*_EL*", order=3)
public WireTap wireTap() {
If you follow approach #1, both interceptors are actually being loaded... just in different ApplicationContexts.
#ContextHierarchy instructs the Spring TestContext Framework to load a hierarchy of contexts.
Thus, the two configuration setups you have are not identical with regard to contexts. When you use #ContextHierarchy, the XML components can only see other beans defined in the XML ApplicationContext (i.e., the top level of the hierarchy); whereas, the Java DSL components can see all components (i.e., those configured in Java Config and those configured in XML, since the XML context is the parent of the Java Config context).
I believe this is actually what you want...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SpringConfiguration.class)
public class SampleTest { /* ... */ }
#Configuration
#ComponentScan(basePackages = "com.model")
#EnableIntegration
#IntegrationComponentScan
#ImportResource("/applicationContext.xml")
public class SpringConfiguration { /* ... */ }
If you don't want SpringConfiguration to import the XML config, you can alternatively use the following to achieve the same behavior:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class SampleTest {
#Configuration
#Import(SpringConfiguration.class)
#ImportResource("/applicationContext.xml")
static class Config {}
/* ... */
}
Regards,
Sam (author of the Spring TestContext Framework)
For example, now in each test class I have to do
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class)
I want to get rid of
#ContextConfiguration(loader=AnnotationConfigContextLoader.class)
and want Spring to scan all the beans in my project.
How can I do that?
You can do this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class MyTest {
#Test
public void testSomething() {
}
#Configuration
#ComponentScan("basepackage")
public static class SpringConfig {
}
}
By default #ContextConfiguration will look for static inner classes annotated with #Configuration, which is why this set up will just work.
You can get rid of loader param altogether, that is not required
If you have your spring configuration in an xml file you would use something like:
#ContextConfiguration(locations="classpath:applicationContext.xml")
If you use Java Config then you would use
#ContextConfiguration(classes=Config.class)
I used generic names in the above samples, you'll of course need to adapt to your project's configuration.
In both cases Spring's component scanning will need to be enabled for Spring to pickup the annotated classes.
You can also simply add #SpringBootTest if using Spring Boot.
#TestConfiguration
#ComponentScan("basepackage")
public class TestConfig{
}
Adding a config class lets spring to load application context.
This solved this issue for me.