How to enforce loading order of spring configuration classes? - java

I'm working with spring-boot on a multi module project (maven). Each module has it's own #Configuration class. Basically I do have the following layout
Module foo-embedded (runs just calls the SpringApplication.run()) method:
#Configuration
#EnableAutoConfiguration
#ComponentScan("de.foobar.rootpackage")
#Import({ApplicationConfig.class, RepositoryConfig.class, SecurityConfig.class})
public class FooApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(FooApplication.class, args);
}
}
Module foo-common (contains all beans and spring-data-jpa initialization config)
#Configuration
#EnableJpaRepositories
#EnableTransactionManagement(entityManagerFactoryRef="entityManagerFactory")
public class RepositoryConfig {
#Bean(destroyMethod = "shutdown")
public DataSource getDataSource() {
// returning a Hikari CP here
}
#Bean(name = "entityManagerFactory") // overriding spring boots default
public EntityManagerFactory getEntityManagerFactory() {
// returning a new LocalEntityManagerFactoryBean here
}
}
Module foo-security (containing spring-securiy configuration and related domain classes), which has a maven dependency on foo-common
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// configuring HTTP security and defining my UserDetailsService Bean
}
When I start the application using the FooApplication class, everything works as expected. The above mentioned UserDetailsServiceImpl get's autowired with my UserRepository which is being created through the #EnableJpaRepositories annotation.
Since I want to write some integration tests I've added a test clss to one of my modules.
Module foo-media (containing some domain related stuff plus test cases for that module)
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {RepositoryConfig.class, SecurityConfig.class})
#WebAppConfiguration
#IntegrationTest
public class DirectoryIntegrationTest {
// my test code
}
When I run the test it seems that the SecurityConfiguration get's loaded before the RepositoryConfig.class does. Since the security config defined the UserServiceImpl which must be autowired, the test fails to start with a
NoSuchBeanDefinitionException telling me: No qualifying bean of type [com.foo.rootpackage.security.repository.UserRepository]
I already tried to add #DependsOn("UserRepository") at the bean definition of UserDetailsService, telling me that spring can't find a bean by that name.
Any hints or help would be greatly appreciated! Thanks in advance!
---- EDIT (since I was asked to provide more code) ----
For testing I do not use the actual RepositoryConfig.class, but have a TestRepositoryConfig.class in the common module. Looking like this
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory", basePackages = "de.foobar.rootpackage")
public class TestRepositoryConfig extends RepositoryConfig {
#Bean
#Override
public DataSource getDataSource() {
// returning the ds for testing
}
}

You can use #Order annotation on your configuration classes to define load ordering. But it's strange because Spring should resolve proper order - so please check if you property inject UserRepository in UserDetailsService

So I was able to solve this. As it pointed out it had nothing to do with the loading order of the configuration classes (which was my first thought).
As you may notice, the only Configuration that had a #ComponentScan annotation was the FooApplication.class
Spring was not able to find the Repositories, as it didn't know where to look for. Providing the basePackages attribute like this:
#EnableJpaRepositories(basePackages = "de.foobar.rootpackage")
at the TestRepositoryConfig.class did the trick here.

Related

Spring #ConditionalOnBean is not finding the the bean

I am using spring boot: 2.5.7
I have a starter in my application that is not working.
I opened the code in order to understand the reason and notice a weird behavior:
The starter code is:
AutoConfiguration:
#Configuration
#EnableConfigurationProperties(MyConfigProperties.class)
public class MyCompanyAutoConfiguration {
#Bean
#ConditionalOnBean(MyConfigProperties.class)
public MeterRegistry defaultNewRelicMeterRegistry(MyConfigProperties config) {
System.out.println("called method");
return MyCompanyNewRelicRegistry.builder(config).build();
}
}
PropertyBean
#Configuration
#ConfigurationProperties(prefix = "company.config.newrelic")
#ConditionalOnProperty(prefix = "company.config.newrelic", name = "apiKey")
#ConditionalOnClass(NewRelicRegistryConfig.class)
public class MyConfigProperties implements NewRelicRegistryConfig {
// getters and setters
...
}
Factory file (spring.factories)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.company.MyCompanyAutoConfiguration
When I start my application, MyCompanyNewRelicRegistry bean is not been created.
I find out that "ConditionalOnBean" is the problem, the code does not print "called method" when use this annotation. It is weird because MyConfigProperties bean is initialized (I saw calling the applicationContext.getBean after container have started).
When I remove the annotation #ConditionalOnBean, everything works well and the method is called.
Why ConditionalOnBean is not finding MyConfigProperties that time?
Note: all properties and classes used in other conditions are ok.
# UPDATE
I changed to:
#Configuration
#EnableConfigurationProperties
#ComponentScan
public class MyCompanyAutoConfiguration {
#Bean
#ConditionalOnBean(MyConfigProperties.class)
public MeterRegistry defaultNewRelicMeterRegistry(MyConfigProperties config) {
System.out.println("called method");
return MyCompanyNewRelicRegistry.builder(config).build();
}
}
The above example works.
I saw in log that spring try to load "defaultNewRelicMeterRegistry" first, when evaluate the conditionalBean, its not created yet.
When I remove the ConditionalOnBean the spring will load the property in order to inject it.
May the componentScan "force" spring to load all other beans before the declared bean in autoconfiguration class, make sense?

Spring boot 2.1 bean override vs. Primary

With Spring Boot 2.1 bean overriding is disabled by default, which is a good thing.
However I do have some tests where I replace beans with mocked instances using Mockito. With the default setting Tests with such a configuration will fail due to bean overriding.
The only way I found worked, was to enable bean overriding through application properties:
spring.main.allow-bean-definition-overriding=true
However I would really like to ensure minimal bean definition setup for my test configuration, which would be pointed out by spring with the overriding disabled.
The beans that I am overriding are either
Defined in another configuration that imported into my test configuration
Auto-discovered bean by annotation scanning
What I was thinking should work in the test configuration overriding the bean and slap a #Primary on it, as we are used to for data source configurations. This however has no effect and got me wondering: Is the #Primary and the disabled bean overriding contradictory?
Some example:
package com.stackoverflow.foo;
#Service
public class AService {
}
package com.stackoverflow.foo;
public class BService {
}
package com.stackoverflow.foo;
#Configuration
public BaseConfiguration {
#Bean
#Lazy
public BService bService() {
return new BService();
}
}
package com.stackoverflow.bar;
#Configuration
#Import({BaseConfiguration.class})
public class TestConfiguration {
#Bean
public BService bService() {
return Mockito.mock(BService.class);
}
}
spring.main.allow-bean-definition-overriding=true can be placed in test configurations. If you need extensive integration testing, you will need to override beans at some point. It's inevitable.
Though the correct answer has already been provided, it implies that your bean will have different names. So, technically, it's not an override.
If you need a real override (because you use #Qualifiers, #Resources or something similar), since Spring Boot 2.X is only possible using the spring.main.allow-bean-definition-overriding=true property.
Update:
Be careful with Kotlin Bean Definition DSL. In Spring Boot it will require a custom ApplicationContextInitializer, like so:
class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {
override fun initialize(context: GenericApplicationContext) =
beans.initialize(context)
}
Now if you decide to override one of such DSL-based beans in your test via #Primary #Bean method, it will not do. The initializer will kick in after #Bean methods and you'd still get the initial, DSL-based bean in your tests even with #Primary on the test #Bean.
One other option would be to also create a test initializer for your tests and list them all in your test properties, like so(order matters):
context:
initializer:
classes: com.yuranos.BeansInitializer, com.yuranos.TestBeansInitializer
Bean Definition DSL also supports primary property via:
bean(isPrimary=true) {...}
- which you'll need to eliminate ambiguity when you try to inject a bean, however main:allow-bean-definition-overriding: true is not needed if you go pure DSL way.
(Spring Boot 2.1.3)
Overriding beans means that there may be only one bean with a unique name or id in the context. So you can provide two beans in the following way:
package com.stackoverflow.foo;
#Configuration
public class BaseConfiguration {
#Bean
#Lazy
public BService bService1() {
return new BService();
}
}
package com.stackoverflow.bar;
#Configuration
#Import({BaseConfiguration.class})
public class TestConfiguration {
#Bean
public BService bService2() {
return Mockito.mock(BService.class);
}
}
If you add #Primary then primary bean will be injected by default in:
#Autowired
BService bService;
I make the testing beans available only in test profile, and allow overriding for just while testing, like this:
#ActiveProfiles("test")
#SpringBootTest(properties = {"spring.main.allow-bean-definition-overriding=true"})
class FooBarApplicationTests {
#Test
void contextLoads() {}
}
The bean I am mocking in the test configuration:
#Profile("test")
#Configuration
public class FooBarApplicationTestConfiguration {
#Bean
#Primary
public SomeBean someBean() {
return Mockito.mock(SomeBean.class);
}
}
It is allowed to override #Component with #Bean by default. In your case
#Service
public class AService {
}
#Component
public class BService {
#Autowired
public BService() { ... }
}
#Configuration
#ComponentScan
public BaseConfiguration {
}
#Configuration
// WARNING! Doesn't work with #SpringBootTest annotation
#Import({BaseConfiguration.class})
public class TestConfiguration {
#Bean // you allowed to override #Component with #Bean.
public BService bService() {
return Mockito.mock(BService.class);
}
}

NoSuchBeanDefinitionException after migrate from spring boot 1.5 to 2.0

Today I want to upgrade from spring boot 1.5.7 to 2.0.1 but at runtime spring cannot find any of my spring-beans that are outside of my ApplicationConfiguration class even if I mentioned these package on SpringBootApplication (scanBasePackages) annotation.
These are the properties of the versions of my pom.xml file:
<properties>
<hibernate.version>5.2.10.Final</hibernate.version>
<spring.version>5.0.5.RELEASE</spring.version>
<springSecurity.version>5.0.4.RELEASE</springSecurity.version>
<springBoot.version>2.0.1.RELEASE</springBoot.version>
<jackson.version>2.9.1</jackson.version>
<slf4j.version>1.7.13</slf4j.version>
<logback.version>1.1.3</logback.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
This is my ApplicationConfiguration class:
package t.i.config;
#SpringBootApplication(scanBasePackages = {
"t.i.DAO",
"t.i.SERVICES",
"t.i.config"
})
#EnableAutoConfiguration(exclude = {
/*HibernateJpaAutoConfiguration.class*/ // old spring-boot 1.5 class
})
#PropertySource({ "classpath:application.mssql.properties" })
#EnableCaching
#EnableTransactionManagement
public class ApplicationConfiguration {
#Primary
#Autowired
#Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) {
... code there that need MultiTenantConnectionProvider ...
}
}
SessionFactory bean need MultiTenantConnectionProvider which exists in package t.i.config.multitenancy:
package t.i.config.multitenancy;
#Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
... code there ...
}
Despite the annotation on configuration class:
#SpringBootApplication(scanBasePackages = "t.i.config")
Spring throw exception at runtime:
NoSuchBeanDefinitionException: No qualifying bean of type 'org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider' available
This is an exemple with MultiTenantConnectionProvider but spring does not found any of my beans (repositories and services) if they are not explicitly declared in ApplicationConfiguration file.
Maybe it is not important, but I launch my app as a jar and my class SpringBootApplicationLauncher handle commandlines parameters and context loading:
package t.i;
public class SpringBootApplicationLauncher {
public static void main(String[] args) throws Exception {
SpringApplication springApp = new SpringApplication(new Class[]{t.i.config.ApplicationConfiguration.class});
context = springApp.run(args);
}
}
Where I was wrong ? EDIT: It work thanks to Norbert Bicsi answer.
Since you are defining your own datasource in the multi-tenancy app, you cannot let Spring Boot do its magic of autoconfiguration of data sources and connection to databases as defined in the datasources properties.
You should be excluding the DataSourceAutoConfiguration class.
So you need to add the exclude attribute as follows:
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
I have a recently created Spring Boot 2 (with Hibernate) and Spring Security 5 based database-per-tenant multitenancy web app with complete source code. Take a look and let me know if it helps.
You could try the basic approach where you leave you application class at the root level in your project so the #SpringBootApplication does a component scan of all the packages next to it and all subpackages.
Or you could try with the component scan annotation like:
#ComponentScan(basePackages = {
"t.i.DAO",
"t.i.SERVICES",
"t.i.config"
})
#SpringBootApplication
public class ApiApplication {
// ...
}

#EnableFeignClients and #FeignClient fail on Autowiring 'FeignContext' NoSuchBeanException

The microservice I'm writting needs to communicate to other microservices in our platform. On that attempt, the ideal solution for us is Spring Cloud Netflix Feign, implemeting a #FeignClient.
However, I'm facing the exception below when I try an #Autowired ReviewProvider:
Exception (cause)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.netflix.feign.FeignContext' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:353)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1093)
at org.springframework.cloud.netflix.feign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:155)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168)
ReviewProvider.java
#FeignClient("http://metadata-reviews")
public interface ReviewProvider {
#RequestMapping(path = "sessions", method = POST)
ReviewSessionDTO createSession();
}
ReviewProvider.java
#RunWith(SpringRunner.class)
#ActiveProfiles(INTEGRATION)
#ContextConfiguration(classes = AppEntry.class)
#AutoConfigureTestDatabase(replace = Replace.NONE)
#DataJpaTest
public class ReviewProviderTest {
#Autowired
private ReviewProvider provider;
private Class<? extends ReviewProvider> providerClass;
#Before
public void setup() {
providerClass = provider.getClass();
}
#Test
public void classAnnotations() {
assertTrue(providerClass.isAnnotationPresent(FeignClient.class));
assertEquals("http://metadata-reviews", providerClass.getAnnotation(FeignClient.class).value());
}
#Test
public void createSession() throws Exception {
final Method method = providerClass.getDeclaredMethod("createSession");
assertTrue(method.isAnnotationPresent(RequestMapping.class));
final RequestMapping mapping = method.getAnnotation(RequestMapping.class);
assertEquals("sessions", mapping.path());
assertEquals(0, method.getParameters().toString());
}
}
Seems like there is not anything out there yet about the solution to this stuff...
Here is what I did to solve this:
Add this annotation to your test class:
#ImportAutoConfiguration({RibbonAutoConfiguration.class, FeignRibbonClientAutoConfiguration.class, FeignAutoConfiguration.class})
Try it, if it does not work, you might need the #EnableFeignClients annotation on your main program config
Recommended approach is to slice application configuration, this means you need to remove #EnableFeignClients from SpringBootApplication.
and add dedicated configuration class:
#Configuration
#EnableFeignClients
public class CloudConfiguration {
}
This is required, because all slices annotation (like #WebMvcTest) include default configuration from SpringBootApplication.
Reference:
https://github.com/spring-projects/spring-boot/issues/7270
https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#test-auto-configuration
I solved this problem just by #EnableAutoConfiguration annotation on my class
The only what you must to do:
add to your build file feign dependency, for example, for gradle
compile 'org.springframework.cloud:spring-cloud-starter-feign'
add #FeignClient to your interface
add #EnableFeignClients to any configuration or to class with annotation #SpringBootApplication
I also faced this issue I followed MariuszS answer. However had to figure out couple of things so putting here... we need to tell #SpringBootTest from where to pick up the configuration so it would be like this
#SpringBootTest
#ContextConfiguration(classes = { YourClientConfig.class })
I had to add base packages and configuration
#Configuration
#EnableFeignClients( basePackages = ["com.yourcompany.yourproject"])
internal open class FeignConfiguration

Overriding beans in Integration tests

For my Spring-Boot app I provide a RestTemplate though a #Configuration file so I can add sensible defaults(ex Timeouts). For my integration tests I would like to mock the RestTemplate as I dont want to connect to external services - I know what responses to expect. I tried providing a different implementation in the integration-test package in the hope that the latter will override the real implementation , but checking the logs it`s the other way around : the real implementation overrides the test one. How can I make sure the one from the TestConfig is the one used?
This is my config file :
#Configuration
public class RestTemplateProvider {
private static final int DEFAULT_SERVICE_TIMEOUT = 5_000;
#Bean
public RestTemplate restTemplate(){
return new RestTemplate(buildClientConfigurationFactory());
}
private ClientHttpRequestFactory buildClientConfigurationFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(DEFAULT_SERVICE_TIMEOUT);
factory.setConnectTimeout(DEFAULT_SERVICE_TIMEOUT);
return factory;
}
}
Integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#WebAppConfiguration
#ActiveProfiles("it")
public abstract class IntegrationTest {}
TestConfiguration class:
#Configuration
#Import({Application.class, MockRestTemplateConfiguration.class})
public class TestConfiguration {}
And finally MockRestTemplateConfiguration
#Configuration
public class MockRestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
return Mockito.mock(RestTemplate.class)
}
}
Since Spring Boot 1.4.x there is an option to use #MockBean annotation to fake Spring beans.
Reaction on comment:
To keep context in cache do not use #DirtiesContext, but use #ContextConfiguration(name = "contextWithFakeBean") and it will create separate context, while it will keep default context in cache. Spring will keep both (or how many contexts you have) in cache.
Our build is this way, where most of the tests are using default non-poluted config, but we have 4-5 tests that are faking beans. Default context is nicely reused
1.
You can use #Primary annotation:
#Configuration
public class MockRestTemplateConfiguration {
#Bean
#Primary
public RestTemplate restTemplate() {
return Mockito.mock(RestTemplate.class)
}
}
BTW, I wrote blog post about faking Spring bean
2.
But I would suggest to take a look at Spring RestTemplate testing support. This would be simple example:
private MockRestServiceServer mockServer;
#Autowired
private RestTemplate restTemplate;
#Autowired
private UsersClient usersClient;
#BeforeMethod
public void init() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void testSingleGet() throws Exception {
// GIVEN
int testingIdentifier = 0;
mockServer.expect(requestTo(USERS_URL + "/" + testingIdentifier))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(TEST_RECORD0, MediaType.APPLICATION_JSON));
// WHEN
User user = usersClient.getUser(testingIdentifier);
// THEN
mockServer.verify();
assertEquals(user.getName(), USER0_NAME);
assertEquals(user.getEmail(), USER0_EMAIL);
}
More examples can be found in my Github repo here
The Problem in your configuration is that you are using #Configuration for your test configuration. This will replace your main configuration. Instead use #TestConfiguration which will append (override) your main configuration.
46.3.2 Detecting Test Configuration
If you want to customize the primary configuration, you can use a
nested #TestConfiguration class. Unlike a nested #Configuration class,
which would be used instead of your application’s primary
configuration, a nested #TestConfiguration class is used in addition
to your application’s primary configuration.
Example using SpringBoot:
Main class
#SpringBootApplication() // Will scan for #Components and #Configs in package tree
public class Main{
}
Main config
#Configuration
public void AppConfig() {
// Define any beans
}
Test config
#TestConfiguration
public void AppTestConfig(){
// override beans for testing
}
Test class
#RunWith(SpringRunner.class)
#Import(AppTestConfig.class)
#SpringBootTest
public void AppTest() {
// use #MockBean if you like
}
Note: Be aware, that all Beans will be created, even those that you override. Use #Profile if you wish not to instantiate a #Configuration.
#MockBean and bean overriding used by the OP are two complementary approaches.
You want to use #MockBean to create a mock and forget the real implementation : generally you do that for slice testing or integration testing that doesn't load some beans which class(es) you are testing depend on and that you don't want to test these beans in integration.
Spring makes them by default null, you will mock the minimal behavior for them to fulfill your test.
#WebMvcTest requires very often that strategy as you don't want to test the whole layers and #SpringBootTest may also require that if you specify only a subset of your beans configuration in the test configuration.
On the other hand, sometimes you want to perform an integration test with as many real components as possible, so you don't want to use #MockBean but you want to override slightly a behavior, a dependency or define a new scope for a bean, in this case, the approach to follow is bean overriding :
#SpringBootTest({"spring.main.allow-bean-definition-overriding=true"})
#Import(FooTest.OverrideBean.class)
public class FooTest{
#Test
public void getFoo() throws Exception {
// ...
}
#TestConfiguration
public static class OverrideBean {
// change the bean scope to SINGLETON
#Bean
#Scope(ConfigurableBeanFactory.SINGLETON)
public Bar bar() {
return new Bar();
}
// use a stub for a bean
#Bean
public FooBar BarFoo() {
return new BarFooStub();
}
// use a stub for the dependency of a bean
#Bean
public FooBar fooBar() {
return new FooBar(new StubDependency());
}
}
}
With #Primary annotation, Bean overriding works with Spring Boot 1.5.X but fails with Spring Boot 2.1.X it throw error:
Invalid bean definition with name 'testBean' defined in sample..ConfigTest$SpringConfig:..
There is already .. defined in class path resource [TestConfig.class]] bound
Please add below properties= which will instruct Spring explicitly to allow overriding, it is self explainatory.
#SpringBootTest(properties = ["spring.main.allow-bean-definition-overriding=true"])
UPDATE: You can add the same property in application-test.yml (file name depend upon what test profile name you are tests with)
Getting a little deeper into it, see my second answer.
I solved the Problem using
#SpringBootTest(classes = {AppConfiguration.class, AppTestConfiguration.class})
instead of
#Import({ AppConfiguration.class, AppTestConfiguration.class });
In my case the Test is not in the same package as the App. So I need to specify the AppConfiguration.class (or the App.class) explicit. If you use the same package in the test, than I guess you could just write
#SpringBootTest(classes = AppTestConfiguration.class)
instead of (not working)
#Import(AppTestConfiguration.class );
It is pretty wired to see that this is so different. Maybe some one can explain this. I could not find any good answers until now. You might think, #Import(...) is not picked up if #SpringBootTestsis present, but in the log the overriding bean shows up. But just the wrong way around.
By the way, using #TestConfiguration instead #Configuration also makes no difference.
I´ve declared an inner configuration class within my test because I wanted to overwrite just a single method
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FileNotificationWebhookTest{
public static class FileNotificationWebhookTestConfiguration {
#Bean
#Primary
public FileJobRequestConverter fileJobRequestConverter() {
return new FileJobRequestConverter() {
#Override
protected File resolveWindowsPath(String path) {
return new File(path);
}
};
}
}
}
However,
Declaring the configuration in #SpringBootTest did not work:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = {FileNotificationWebhookTest.FileNotificationWebhookTestConfiguration.class})
or annotating the test configuration with #Configuration did not work:
#Configuration
public static class FileNotificationWebhookTestConfiguration {
}
and was leading to
Caused by: org.springframework.context.ApplicationContextException:
Unable to start web server; nested exception is
org.springframework.context.ApplicationContextException: Unable to
start ServletWebServerApplicationContext due to missing
ServletWebServerFactory bean.
What did work for me ( contrary to some other posts here) was using #Import
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Import(FileNotificationWebhookTest.FileNotificationWebhookTestConfiguration.class)
class FileNotificationWebhookTest {
}
Using Spring: 5.3.3 with Spring-Boot-Starter: 2.4.2
#MockBean creates Mockito mock instead of production build.
If you do not want to use Mockito, but provide a replacement in some other way (i.e. by disabling some features of bean with feature toggles), I suggest using combination of #TestConfiguration (since Spring Boot 1.4.0) and #Primary annotation.
#TestConfiguration will load your default context and apply your #TestConfiguration piece in addition to it. Adding #Primary will force your mocked RestTemplate to be injected to it's dependents.
See simplified example below:
#SpringBootTest
public class ServiceTest {
#TestConfiguration
static class AdditionalCfg {
#Primary
#Bean
RestTemplate rt() {
return new RestTemplate() {
#Override
public String exec() {
return "Test rest template";
}
};
}
}
#Autowired
MyService myService;
#Test
void contextLoads() {
assertThat(myService.invoke()).isEqualTo("Test rest template");
}
}
This is super weird.
In my case, (Spring Boot 2.6.7), I could simply #Import MyTestConfiguration containing a custom #Primary #Bean into my #SpringBootTest, and everything worked.
Right until I needed to explicitly name my bean.
Then I suddenly had to resort to
#SpringBootTest(
properties = ["spring.main.allow-bean-definition-overriding=true"],
classes = [MyTestConfig::class],
)
Check this answer along with others provided in that thread.
It's about overriding bean in Spring Boot 2.X, where this option was disabled by default. It also has some ideas about how to use Bean Definition DSL if you decided to take that path.
The simplest solution I found was to set this property in application.properties:
spring.main.allow-bean-definition-overriding=true
This will enable overriding of beans.
Next, create a configuration class in test, and annotate your bean with:
#Bean
#Primary
This way, this bean will override your usual bean when running tests.

Categories