CamelTestSupport with routes using classes with #Autowired - spring-boot

After help from experts over at question Camel unit test with cametestsupport, template is always null, I ended up with one more issue.
Below is my test class - a simple test that tests a route which has only ValidationProcessor POJO.
public class RouteTests extends CamelTestSupport {
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
from("direct:start")
.filter().method(ValidationProcessor.class, "validate")
.to("mock:result");
}
};
}
#Test
public void testSendMatchingMessage() throws Exception {
ObjectMapper objectmapper = new ObjectMapper();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream stream = loader.getResourceAsStream("test.json");
JSONObject testJson = new JSONObject(objectmapper.readValue(stream, Map.class));
MockEndpoint resultEndpoint = getMockEndpoint("mock:result");
resultEndpoint.expectedMessageCount(1);
template.sendBody("direct:start", testJson);
resultEndpoint.assertIsSatisfied();
}
}
The problem comes when this ValidationProcessor has an #Autowired component in it. My validation method needs data from Elasticsearch and hence I have an #Autowired for an elastic client.
When I run mvn clean test, I am getting a NullPointerException stating that this elastic client is null. I think the issue is that this test is devoid of anything to do with Spring and hence the issue, but I do not know how to overcome this.
My test needs to fetch data from Elasticsearch and hence the ValidationProcessor POJO does need #Autowired.

When you extend CamelTestSupport then it's not a Spring application. You need to extend CamelSpringTestSupport. That would create Camel in a Spring runtime, and then allow beans to have IoC via Spring. This kind of testing is often used with Camel XML routes where the routes are defined in XML files. However, you can have a plain XML file and refer to routes in Java DSL as well.
However, as Makoto answers, then vanilla Spring unit testing is of late often about using all those gazillion annotations. Camel has support for that as well as his answer shows. This is also how for example Spring Boot testing is done, etc.
You can find some unit tests in camel-test-spring you can use as inspiration as well.

What I've discovered is that it's unwise to extend CamelTestSupport when you want to use anything with Spring. In fact, there's a better way to do it - use the CamelSpringBootRunner instead.
Well...I say "better". You're going to find yourself attaching a ton of annotations. Of the things you'll need:
A boostrapper to ensure that you're bootstrapping Camel correctly
The routes you wish to add to the classpath (and all of the beans); this ensures that they get added to Camel's registry through Spring's registry
You have to dirty the context after every run or your tests get into a wonky state.
You can automock endpoints by specifying either #MockEndpoints or #MockEndpointsAndSkip. The former sends the data along to the actual route.
Below is just a start. Many of these annotations and their documentation can be found in the formal docs.
#RunWith(CamelSpringBootRunner.class)
#BootstrapWith(SpringBootTestContextBootstrapper.class)
#ActiveProfiles("test")
#SpringBootTest(classes = { YourEntryPointClass.class })
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#UseAdviceWith
#MockEndpoints("direct:*")
public class RouteTests {
}

Related

How to expose endpoints REST lazily?

Scenario:
The Spring Boot application should expose its REST endpoints
only when a specific action occurs.
Is there any way in Spring to lazily expose endpoints, or even the whole HTTP subsystem?
In Apache CXF, we can do something like this:
void exposeEndpoints() {
EndpointImpl endpoint = new EndpointImpl(cxfBus, serviceImpl);
endpoint.publish();
}
How to do the same thing in Spring?
You could take a look at #RefreshScope.
I would define my #RestController beans as follows:
#Configuration
#RefreshScope
public ControllerConfig {
#Bean
#ConditionalOnProperty(value = "should.initialize.rest", havingValue = true)
public SomeController someController(){
....
}
#Bean
#ConditionalOnProperty(value = "should.initialize.rest", havingValue = true)
public SomeOtherController someOtherController(){
....
}
}
and if you start your application with property should.initialize.rest with value false in your application.properties:
should.initialize.rest=false
Then your controllers won't be registered/initialized. When the application is running, you could then update your application.properties to:
should.initialize.rest=true
and make a call to /refresh, then your ApplicationContext will reload/refresh, this time with your REST controllers. You can find more about #RefreshScope below:
https://www.baeldung.com/spring-reloading-properties
https://andressanchezblog.wordpress.com/2016/09/15/refresh-scope-in-spring-cloud/
The solution I have provided below is one of the ways and it may or may not be suitable in your case.
It situation seems like more of design concern rather than particular implementation.
I would suggest you to go with little change in the design.
Store the event in DB or Session (as per requirement) as Boolean.
Create Aspect using AspectJ or Spring's own AOP.
Create Before aspect with specific package pointcuts.
In the #Before advice, check for the boolean flag, if the condition for publishing
satisfies than use joinpoint.proceed() else throw some kind of error saying service not available.
Another way is to create custom Annotation with Aspects. You can use that annotation as per requirement and not on the whole service layer.
The benefit of first approach is that you have the control at generic level and the second approach at service level.
I would suggest to create a new child context with your HTTP subsystem. Something like this:
#Service
public class MyBusinessService {
#Autowired
private final ApplicationContext parentContext;
private AnnotationConfigWebApplicationContext webContext;
public void myBusinessMethod() {
this.webContext = new AnnotationConfigWebApplicationContext();
this.webContext.setParent(parentContext);
this.webContext.scan("com.mybusiness.service.webcomponents");
this.webContext.refresh();
this.webContext.start();
}
}
DISCLAIMER: This is proof-of-concept code, I did not try to compile or run this. But hopefully it is enough to illustrate the concept.

Is it worthy using AOP in a Spring-boot application?

A few days ago, I posted this question asking whether there is a newer approach to Spring AOP and mockito.
While I understand how to use AOP, I am still missing on its returned value. The whole endeavour has shown me that it's not really popular - at least there aren't that many recent posts.
If I comment out the annotations #Aspect and #Configuration in my LoggingAspect class, effectively rendering it non-aop, all my tests are green. If I switch it back on, I start getting a load of NullPointerExceptions and loads of other errors on my mocked test classes.
I wonder if it is worth the hassle.
EDIT adding more detail from my specific implementation.
Controller:
#RestController
public class EndpointController {
private EndpointService endpointService;
#Autowired
public EndpointController(EndpointService endpointService) {
this.endpointService = endpointService;
}
#PostMapping(path = "/endpoint", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
private #ResponseBody EndpointResponse doSomething(//... //, #RequestBody SomeObject someObject) throws Exception {
return endpointService.doSomething(someObject);
}
}
In my test class, I have:
#RunWith(SpringRunner.class)
public class EndpointControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void shouldBeSuccessfulAccessingTheEndpoint() throws Exception {
SomeObject someObject = new SomeObject(// values //);
ObjectMapper mapper = new ObjectMapper();
String payload = mapper.writeValueAsString(someObject);
mockMvc.perform(post("/endpoint").contentType(MediaType.APPLICTION_JSON).content(payload)).andExpect(status().isOK));
}
}
It fails and throws a NullPointerException. When debugging, the endpointService is always null.
AOP is great for cross-cutting concerns:
Logging (we use it for access and performance logging)
Validation (such as used by Bean Validation, JSR-380)
Transactional scopes (built into frameworks such as JEE and Spring)
Security checks (e.g. Shiro)
and many more.
It could be used for other purposes, such as extending/wrapping existing functionality, though that is definitely not something I'd recommend, and fortunately never became popular, as it seems.
AOP is as valid as ever. It's used for transactions, logging, metrics, etc.
I think there was period where it might have been overused as decorators.
Production and testing are different matters.
If you're unit testing a class, it suggests that you aren't testing the aspects. You could make those aspects conditional based on profile.
If the proper operation of your object depends on the aspect, because it modifies the input, perhaps you should rethink.

Spring Proxy Creation of Classes annotated with #Configuration or #Component

Spring uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. If a class is annotated with #Configuration, then CGLIB is used.
However, one limitation of Spring AOP is that once the call has finally reached the target object, any method calls that it may make on itself are going to be invoked against the this reference, and not the proxy. This piece of information is important to remember when using #Transactional and in other places as well.
So having that knowledge, in the code below, is Spring injecting the actual instance or the proxy of SimpleBean?
#Configuration
public class Config {
#Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
#Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean()); //<---
}
}
And what is the behavior if a class is annotation with #Component?
Let me give you another perspective.
Say there is an another bean AnotherBeanConsumer that also needs a simpleBean. Simple Bean has a Singleton scope:
#Configuration
public class Config {
#Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
#Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
#Bean
public AnotherBeanConsumer anotherBeanConsumer() {
return new AnotherBeanConsumer(simpleBean());
}
}
Now the question is, how its possible that two calls to simpleBean() made from different methods simpleBeanConsumer and anotherBeanConsumer return the same instance of the simple bean (since its a singleton obviously)?
IMO (and disclaimer, I'm not affiliated with spring or something), This is the main reason of creating proxies that wrap Configurations.
Now indeed Spring AOP has a limitation of calling methods just as you've stated, however who said that spring under-the-hood uses spring AOP? The bytecode instrumentation done on much lower levels doesn't have a limitation like this. After all creating a proxy means: "create a proxy object that will have the same interface but will alter the behavior", right?
For example if you use CGLIB that uses inheritance you could create a proxy out of configuration that looks like this (schematically):
class CGLIB_GENERATED_PROXY extends Config {
private Map<String, Object> singletonBeans;
public SimpleBean simpleBean() {
String name = getNameFromMethodNameMaybePrecached();
if(singletonBeans.get(name) != null) {
return singletonBeans.get(name);
}
else {
SimpleBean bean = super.simpleBean();
singletonBeans.put(name, bean);
return bean;
}
}
....
}
Of course its only a schematic picture, in real life there is an application context that basically provides the access to the map like this, but you get the point.
If its not enough, then there are some even more sophisticated frameworks that spring must make use of in order to load a configuration (like ASM)...
Here is an example:
If you use #ConditionalOnClass(A.class) and the class doesn't really exist in runtime, how spring can load the bytecode of the configuration that uses this configuration and not fail on something like NoClassDefFoundException?
My point is that it goes far beyond the spring AOP, and has its quirks :)
Having said that, nothing that I've describe above requires the real components to be always wrapped in Proxies of any kind. So in the most trivial case, when SimpleBean does not by itself have some annotations that require proxy generation (stuff like #Cached, #Transactional and so forth), Spring won't wrap the object of that type and you'll get a plain SimpleBean object.

How to Inject custom method argument in Spring WebFlux using HandlerMethodArgumentResolver?

I want to create an custom method argument Resolver using Spring WebFlux. I am following link but its seem to be not working.
I am able to create the custom argument resolver using WebMvc.
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
public class MyContextArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyCustomeObject.class.isAssignableFrom(parameter.getParameterType())
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
.....
return Mono.just(new MyCustomeObject())
}
Please note that i am using HandlerMethodArgumentResolver from .web.reactive. package.
My AutoConfiguration file look like
#Configuration
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the class-path
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)//checks that the app is a reactive web-app
public class RandomWebFluxConfig implements WebFluxConfigurer {
#Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
MyContextArgumentResolver[] myContextArgumentResolverArray = {contextArgumentResolver()};
configurer.addCustomResolver(myContextArgumentResolverArray );
}
#Bean
public MyContextArgumentResolver contextArgumentResolver() {
return new MyContextArgumentResolver ();
}
My spring.factories looks like
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.XXXX.XXX.XXX.RandomWebFluxConfig
Please note that above configuration is part of the jar which is added in Spring WebFlux Boot project enabled using #EnableWebFlux .
It seems you're conflating two different problems here.
First, you should make sure that your method argument resolver works in a regular project.
For that, you need a #Configuration class that implements the relevant method in WebFluxConfigurer. Your code snippet is doing that but with two flaws:
Your configuration is using #EnableWebFlux, which is disabling the WebFlux auto-configuration in Spring Boot. You should remove that
it seems you're trying to cast a list of MethodArgumentResolver into a single instance and that's probably why things aren't working here. I believe your code snippet could be just:
configurer.addCustomResolver(contextArgumentResolver());
Now the second part of this question is about setting this up as a Spring Boot auto-configuration. I guess that you'd like WebFlux applications to automatically get that custom argument resolvers if they depend on your library.
If you want to achieve that, you should first make sure to read up a bit about auto-configurations in the reference documentation. After that, you'll realize that your configuration class is not really an auto-configuration since it will be applied in all cases.
You should probably add a few conditions on that configuration like:
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the classpath
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) // checks that the app is a reactive web app

Define ElasticsearchIntegrationTest client in the test context xml

In our application we are integrating ES.
There is a repository with an ElasticsearchTemplate injected:
#Repository
public class ElasticSearchRepositoryImpl implements ElasticSearchRepository {
private ElasticsearchTemplate elasticsearchTemplate;
#Autowired
public ElasticSearchRepositoryImpl(ElasticsearchTemplate elasticsearchTemplate){
this.elasticsearchTemplate = elasticsearchTemplate;
}
In the tests side, we are making use of the ElasticsearchIntegrationTest class as defined in the documentation.
The tests have their own context, but since the repository is being annotated with the #Repository annotation, it is being loaded and this makes me define an ElasticsearchTemplate in the test context.
At this point, I dont want to define a template, because if so I would need to define a client and since I am going to use in the tests the client() provided by the ElasticsearchIntegrationTest, this would make no sense.
I have different possibilities:
Exclude in the test context the repository - this is being used by other beans and I would have to exclude many things and deal with a lot of problems, besides I don't think is clean.
Declare the template with no client (I don't like this possibility either):
Use the client provided by the ElasticsearchIntegrationTest in the template definition within the test context - I don't know how to do this, any hint would be welcome.
Any other solution, example or idea that helps me would be very welcome.
Thanks in advance
For my solution 2 I post here my code, could not post it above:
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client"><null /></constructor-arg><!-- TODO: this client should be manually injected in the tests -->
</bean>
From a Spring Application, the easiest way to create an integration test with Elasticsearch is to instanciate an embedded node.
#Configuration
public class MyTestConfiguration {
// create and start an ES node
#Bean
public Client client() {
Settings settings = ImmutableSettings.builder()
.put("path.data", "target/data")
.build();
Node node = NodeBuilder.nodeBuilder().local(true).settings(settings).node();
return node.client();
}
// initialize your ES template
#Bean
public ElasticsearchTemplate elasticsearchTemplate(Client client) {
return new ElasticsearchTemplate(client);
}
}
If you use Spring Boot and Spring Data Elasticsearch, it will create an embedded node automatically when any configuration is provided.

Resources