Spring #ConditionalOnBean is not finding the the bean - java

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?

Related

How to import a bean which comes from a configuration bean conditional on another class?

I'm using a 3rd party dependency in my Spring Boot project (version 2.6.3) which has the following classes:
#ConditionalOnProperty(prefix = "spring.cloud.vault", name = "enabled", matchIfMissing = true)
#AutoConfigureAfter(ApacheHttpClientAutoConfiguration.class)
#Configuration
public class VaultServiceAutoConfiguration {
// ...
}
#Configuration
#EnableConfigurationProperties({VaultServiceProperties.class})
#ConditionalOnBean(VaultServiceAutoConfiguration.class)
public class VaultServiceFacadeConfiguration {
private final VaultServiceProperties vaultServiceProperties;
#Autowired
public VaultServiceFacadeConfiguration(VaultServiceProperties properties) {
this.vaultServiceProperties = properties;
}
#Bean
#ConditionalOnMissingBean(VaultServiceFacade.class)
public VaultServiceFacade vaultServiceFacade(
#Qualifier("runtime") VaultServiceTemplate vaultServiceTemplate) {
// ...
}
}
I set spring.cloud.vault.enabled=true in my bootstrap.yml.
In my project I import the dependency and created a component class:
#Component
public class MyVault {
private VaultServiceFacade vaultServiceFacade;
public MyVault(VaultServiceFacade vaultServiceFacade) {
this.vaultServiceFacade = vaultServiceFacade;
}
}
in order to use VaultServiceFacade bean which provides methods to interact with Hashicorp Vault instance. The problem is that I get this error: No qualifying bean of type 'other.package.VaultServiceFacade' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I don't understand why I get his error because my main class is annotated with #SpringBootApplication so that auto-configuration is enabled. In addition if I try to inject VaultServiceAutoConfiguration bean it works:
#Component
public class MyVault {
private VaultServiceAutoConfiguration v;
public MyVault(VaultServiceAutoConfiguration v) {
this.v = v;
}
}
EDIT: I did notice that the dependency has a file spring.factories with the following code:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
other.package.VaultServiceAutoConfiguration
So maybe only VaultServiceAutoConfiguration is "whitelisted" for auto-configuration, so I need to understand how to allow other beans from other.package to be injected in my project.
EDIT2: I managed to get this working by adding #ComponentScan on the MyVault class:
#ComponentScan("other.package")
#Component
public class MyVault {
private VaultServiceFacade vaultServiceFacade;
public MyVault(VaultServiceFacade vaultServiceFacade) {
this.vaultServiceFacade = vaultServiceFacade;
}
}
I still don't understand why the ComponentScan works only on when added to MyVault class but is if I scan for this package from main it class it doesn't work:
#SpringBootApplication(scanBasePackages = {"other.package"})
public class Application {
// ...
}
#ComponentScan just works because you're very directly asking Spring to scan said package for any beans which it straight up does.
I can't tell why #SpringBootApplication(scanBasePackages = {"other.package"}) doesn't work without trying it for myself but I guess the error you get is that the other.package beans get created and instead all other beans fail to exist (after all other.package is not the base package).
Finally, why doesn't you preferred conditional approach work?
Hard to tell without a simplified sample app to work with. I doubt it's because of VaultServiceAutoConfiguration, I've created a simplified project where the offending bean lies directly in said class and it loads just fine. Try moving your VaultServiceFacade definition to VaultServiceAutoConfiguration and remove #AutoConfigureAfter(ApacheHttpClientAutoConfiguration.class), if that works you know it's something in VaultServiceFacadeConfiguration (or #AutoConfigureAfter(ApacheHttpClientAutoConfiguration.class) itself, maybe #EnableConfigurationProperties({VaultServiceProperties.class})?
Also, I used application.properties instead of bootstrap.yml but that's unlikely to be it.
Feel free to send a sample simplified project to work on and test your assumptions
As the default value for the property is already true, I would not set it at all.
In order to explicitly configure the Facade try to
#Import(VaultServiceFacadeConfiguration.class)
in your SpringBootApplication.
Please check your dependencies. "other.package" looks very strange and does not look like a package name used by HashiCorp.

Why is #Bean(initMethod="") not detecting given method in spring?

Edit Fixed by changing package.
I have this configuration file for spring framework
#Configuration
public class AppConfig {
#Bean(initMethod = "populateCache")
public AccountRepository accountRepository(){
return new JdbcAccountRepository();
}
}
JdbcAccountRepository looks like this.
#Repository
public class JdbcAccountRepository implements AccountRepository {
#Override
public Account findByAccountId(long
return new SavingAccount();
}
public void populateCache() {
System.out.println("Populating Cache");
}
public void clearCache(){
System.out.println("Clearing Cache");
}
}
I'm new to spring framework and trying to use initMethod or destroyMethod. Both of these method are showing following errors.
Caused by: org.springframework.beans.factory.support.BeanDefinitionValidationException: Could not find an init method named 'populateCache' on bean with name 'accountRepository'
Here is my main method.
public class BeanLifeCycleDemo {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new
AnnotationConfigApplicationContext(AppConfig.class);
AccountRepository bean = applicationContext.getBean(AccountRepository.class);
applicationContext.close();
}
}
Edit
I was practicing from a book and had created many packages for different chapters. Error was it was importing different JdbcAccountRepository from different package that did not have that method. I fixed it and it works now. I got hinted at this from answers.
Like you said, if you are mixing configurations types, it can be confusing. Besides, even if you created a Bean of type AccountRepository, because Spring does a lot of things at runtime, it can call your initMethod, even if the compiler couldn't.
So yes, if you have many beans with the same type, Spring can be confused an know which one to call, hence your exception.
Oh and by the way, having a Configuration creating the accountRepoisitory Bean, you can remove the #Repository from your JdbcAccountRepository... It is either #Configuration + #Bean or #Component/Repository/Service + #ComponentScan.
TL;DR
Here is more information and how Spring creates your bean : What object are injected by Spring ?
#Bean(initMethod = "populateCache")
public AccountRepository accountRepository(){
return new JdbcAccountRepository();
}
With this code, Spring will :
Detect that you want to add a Bean in the application Context
The bean information are retrieved from the method signature. In your case, it will create a bean of type AccountRepository named accountRepository... That's all Spring knows, it won't look inside your method body.
Once Spring is done analysing your classpath, or scanning the bean definitions, it will start instanciating your object.
It will therefor creates your bean accountRepository of type AccountRepository.
But Spring is "clever" and nice with us. Even if you couldn't write this code without your compiler yelling at you, Spring can still call your method.
To make sure, try writing this code :
AccountRepository accountRepository = new JdbcAccountRepository();
accountRepository.populateCache(); // Compiler error => the method is not found.
But it works for Spring... Magic.
My recommandation, but you might thinking the same now: If you have classes across many packages to answer different business case, then rely on #Configuration classes. #ComponentScan is great to kickstart your development, but reach its limit when your application grows...
You mix two different ways of spring bean declaration:
Using #Configuration classes. Spring finds all beans annotated with #Configuration and uses them as a reference to what beans should be created.
So if you follow this path of configuration - don't use #Repository on beans. Spring will detect it anyway
Using #Repository - other way around - you don't need to use #Configuration in this case. If you decide to use #Repository put #PostConstruct annotation on the method and spring will call it, in this case remove #Configuration altogether (or at least remove #Bean method that creates JdbcAccountRepository)
Annotate populateCache method with #PostConstruct and remove initMethod from #Bean. It will work.

Spring dependency injection with duplicate beans and #Order annotation

I am moderately confused about the DI injection mechanism in Spring when having multiple beans with the same name/type.
According to the exam slides from the Pivotal's "Core Spring" Course, Spring's behaviour with identical beans can be boiled down to:
One can define same bean more than once
Spring injects the bean defined last
Using #Order, the loading mechanism (and thus, which bean is loaded last) can be modified
However, in the following example, Spring will ignore any #Order annotations and inject the bean from the Config class last mentioned in the #Import statement.
I'm therefore wondering whether the order of config classes in the #Import annotation overrides any #Order annotations. Or do I miss another important point?
Any hints are highly appreciated. Thanks Stack Overflow!
Main Configuration class
#Configuration
#Import({RogueConfig.class,RewardsConfig.class})
public class TestInfrastructureConfig {
// nothing interesting here, just importing configs
}
RewardsConfig
#Configuration
#Order(1)
public class RewardsConfig {
#Bean
public RewardNetwork rewardNetwork() {
System.out.println("This Bean has been loaded from: " + this.getClass().getName());
return new RewardNetworkImpl(null, null, null);
}
}
RogueConfig
#Configuration
#Order(2)
public class RogueConfig {
#Bean
public RewardNetwork rewardNetwork() {
System.out.println("This Bean has been loaded from: " + this.getClass().getName());
return new RewardNetworkImpl(null, null, null);
}
}
Test class
public class RewardNetworkTests {
ApplicationContext applicationContext;
#BeforeEach
void setUp() {
applicationContext = SpringApplication.run(TestInfrastructureConfig.class);
}
#Test
void injectingRewardNetworkBeanWithOrdering() {
RewardNetwork rewardNetwork = applicationContext.getBean(RewardNetwork.class);
assertNotNull(rewardNetwork);
}
}
No matter what values I assign #Order, or if I use ordering at all, the result will always be:
This Bean has been loaded from: config.RewardsConfig$$EnhancerBySpringCGLIB$$62461c55
The only way to change this is to modify the Import annotation in my TestInfrastructureConfig like so:
#Import({RewardsConfig.class,RogueConfig.class}), which yields:
This Bean has been loaded from: config.RogueConfig$$EnhancerBySpringCGLIB$$6ca7bc89
I am wondering what needs to be done to allow the values defined in #Order to take any effect.
I've been able to get Spring to use the #Order annotations by loading the configurations directly ( i.e. without the detour through a #Configuration class using #Import):
#SpringJUnitConfig({RogueConfig.class, RewardsConfig.class})
public class CdiTest {
#Test
public void testCdiWithIdenticalBeans(#Autowired RewardNetwork rewardNetwork) {
assertThat(rewardNetwork).isNotNull();
}
}
With the #Order(2) annotation on the RogueConfig class, this bean got loaded last, as shown in stdout:
This Bean has been loaded from: config.RogueConfig$$EnhancerBySpringCGLIB$$552b937f
It seems that when using #Import in config classes it will load bean definitions in the order provided in the annotation, thus making any #Order annotations on the respective config classes useless.

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);
}
}

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