I'm building a Spring Boot Starter for a college project on Java Reflection and Bytecode alteration.
The Reflection/Bytecode is done now, but it will scan for Spring #Controllers/#RestControllers so it can detect certain annotations to run the process.
My question here is what's the best approach? Seems to me that an annotation processor doesn't quite work nicely, and my idea is to create a #Configuration class. Now I need to ensure that all #Controller beans have been booted before I actually process them and I also need to put the result of this processing in a bean that could already exist.
So for example:
#Configuration
public class TestConfig {
#Autowired //I want to autowire but it may not exist, if the user doesn't define I need to create it
private ExternalAnnotatedRequestsModel model;
#Autowired // needed for the framework to acess spring controllers
private ConfigurableApplicationContext ctx;
#Bean // this can also be overriden since the definitions can be done via yaml
public ExternalRequestsProvider() {
return new AnnotationExternalRequestsProvider(ctx);
}
}
Now I also want that when the ExternalRequestsProvider bean is started, it runs the process method and saves the result in the object in the "model" variable.
Using #EventListener for ApplicationReadyEvent to run your process after Spring is fully configured.
#Configuration
public class ExternalRequestsConfig {
#Autowired
private ExternalAnnotatedRequestsModel model;
#Autowired
private ExternalRequestsProvider provider;
#EventListener(ApplicationReadyEvent.class)
public void onApplicationReady(ApplicationReadyEvent event) {
// do your process
}
}
There are spring boot 2.0.2 configuration
#Configuration
public class ApiConfig {
#Bean
#Profile("!tests")
#ConditionalOnProperty(name = "enabled", havingValue = "true")
public MyService service() {
return new MyServiceImpl();
}
}
... and some controller which should be created and added to application context only if MyService bean is initialized.
#RestController
#ConditionalOnBean(MyService.class)
public class MyController {
#Autowired
private MyService service;
}
It works Ok. But occasionally spring boot skips MyController creating. According to the logs MyService is created, but after any other beans(including all controllers), at the end.
Why boot does not process #Configuration beans prior to #RestController?
Thanks.
Why boot does not process #Configuration beans prior to #Controller?
Thanks.
Because Spring doesn't guarantee that.
As well as #ConditionalOnBean warns about this kind of issue in this specification :
The condition can only match the bean definitions that have been
processed by the application context so far and, as such, it is
strongly recommended to use this condition on auto-configuration
classes only. If a candidate bean may be created by another
auto-configuration, make sure that the one using this condition runs
after.
And you don't use the annotation in an auto-configuration class. You indeed specified it in a class annotated with #RestController.
I think that to achieve your requirement you should move the #RestController bean declaration in a #Configuration class that's imported via an EnableAutoConfiguration entry in spring.factories.
I want to add #Component classes to spring container after ApplicationContext load. But I cannot use BeanFactory. Because I'm using BeanFactory I have to define beans for these classes. But I cannot defined it(If I am not use reflection). Because these classes will load at runtime by ClassLoader.
For example
#Component
public class Service {
private final CustomerService customerService;
private final OrderService orderService;
private final PaymentService paymentService;
#Autowired
public Service(CustomerService customerService, OrderService orderService, PaymentService paymentService) {
this.customerService = customerService;
this.orderService = orderService;
this.paymentService = paymentService;
}
}
In this example Spring create bean for this class while application invoking. There is no need to define bean with #Bean. But what I want is compile spring projects and load those clasess from another project and add to Spring ApplicationContext. So i can autowire these. Otherwise I have to create bean with reflection at runtime. And if I use reflection, I have invoke all dependent class recursively.
Is there any way do it without create beans using reflection at runtime.
If I understand things correctly: you have some classes that are marked with #Component and you want Spring to manage their lifecycle?
Does this help: https://springframework.guru/spring-component-scan/ ? #ComponentScan specifically or in XML config something like:
<context:component-scan base-package="org.example"/>
I found a solution.
ConfigurableApplicationContext context = SpringApplication.run(EventServiceApplication.class, args);
// Load class ...
context.start();
If we run context.start() method after load class, spring create #Component like class beans and put to spring container.
Another solution (This is exact solution):
ConfigurableApplicationContext context = SpringApplication.run(EventServiceApplication.class, args);
List<Class<?>> classes = // load classes
classes
.stream()
.filter(clazz -> clazz.isAnnotationPresent(Component.class) || Arrays.stream(clazz.getAnnotations()).anyMatch(annotation -> annotation.annotationType().isAnnotationPresent(Component.class)))
.forEach(applicationContext::register);
After register the classes, maybe one of your loaded class annoteded with #Configuration and it contains #Bean annotated methods. For register those #Bean methods. You should use
ConfigurationClassPostProcessor configurationClassPostProcessor; // This class is autowireable. Spring has bean for this class at spring bean container.
configurationClassPostProcessor.processConfigBeanDefinitions(applicationContext.getDefaultListableBeanFactory())
I found this solution at Spring Framework source code
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.
We have a project that uses Spring annotations to configure its context.
To test this project we are using Mockito and it's Spring extension.
In tests I need to override some beans with mock/spy version.
With the #Mock/#Spy and #InjectMock annotations I have been able to use spy for beans using autowiring mechanism.
Now I have a third party component which create another Spring context and then merge the 2 contexts together. This third party component retrieve beans using a call to the context:
applicationContext.getBean(key);
In this case, the #Mock/#Spy and #InjectMock combination is not working.
The 'workaround' solution I have put in place is an XML file in which I declare my spied bean:
<mockito:spy beanName="beanToSpy"/>
To stay in the annotation world, I have tried springockito-annotations as mentioned in these similar questions:
Injecting Mockito mocks into a Spring bean
and its duplicate:
How to inject a Mock in a Spring Context
But the bean is not spied or mocked, I've probably a configuration error.
My current setup is:
A base class that is in charge of the Spring config for test:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
#ContextConfiguration(loader= SpringockitoContextLoader.class, locations ={"/config.xml","/test-config.xml"})
public abstract class BaseTest {
//...
}
A test class that would like to use a mocked bean:
public class MyTest extends BaseTest {
#ReplaceWithMock #Autowired MyService myService;
#WrapWithSpy #Autowired OtherService otherService;
#Test public void someTest() {
doReturn(x).when(myService).call();
doReturn(y).when(otherService).process();
}
}
Unfortunately in MyTest, the beans are not replaced by their mock/spy versions, it is the plain old regular version.
EDIT:
The third party component that does the lookup is using its own spring parent context and add the current spring context into its own. The lookup to retrieve the component that I want to be mocked occurs after the context has been fully loaded.
What should I do to properly replace the bean in the context with a mock/spy version ?
What is wrong with the way I'm using #WrapWithSpy / #ReplaceWithMock ?
When does the call to
applicationContext.getBean(key);
happens? Is it possible that it retrieves the bean before the SpringockitoContextLoader has a chance to replace it with a spy?
One solution to stay in annotation world would be to override the bean in java config:
#Bean
public MyBeanType myBeanType() {
return spy(new MyBeanType(...))
}
The more conventional way to perform this is by simply mocking them in the test as required :
public class MyTest extends BaseTest {
#Test public void someTest() {
MyService myMockService = Mockito.mock(MyService.class);
// define when's
// perform verification
}
You can inject using SpringReflectionTestUtils, or if using setter injection just set normally.
If you use a global mocked bean in a test class with multiple tests, the results can get confusing.