Spring Boot Camel Testing - spring-boot

I need to test Camel routes in a Spring Boot Application.
I've the Spring boot main class with all the necessary beans declared in it.
I am using the CamelSpringJUnit4ClassRunner.class.
Added my Spring boot main class in #ContextConfiguration as it contains all the configurations. I don't have a separate configuration class.
I 've autowired CamelContext in my Test class:
#Autowired
CamelContext camelContext;
But the test fails with the error:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'org.apache.camel.CamelContext' available: expected at least 1 bean which qualifies as autowire candidate.
Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}

Try to use the CamelSpringBootRunner.class as the runner and add the #SpringBootTest annotation to the test class.
Example from the Camel repository
UPDATE (based on your comment)
If you change your bootstrapper class to SpringBootTestContextBootstrapper then it should work:
#BootstrapWith(SpringBootTestContextBootstrapper.class)
The equivalent configuration as you have but in this case you don't need to add the ContextConfiguration and the BootstrapWith annotation:
#RunWith(CamelSpringBootRunner.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#MockEndpoints("log:*")
#DisableJmx(false)
#SpringBootTest(classes = MyClass.class)

just enable #EnableAutoConfiguration it will work

With Camel 3.1 Spring Boot 2.2.5 and JUnit5, while also setting test application properties:
#CamelSpringBootTest
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#TestPropertySource(properties = "spring.cloud.consul.enabled=false")
public class CamelRouteTest {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private CamelContext camelContext;
#EndpointInject("mock:bean:userService")
private MockEndpoint mockUserService;
private User user;
#BeforeEach
public void setUp() throws Exception {
AdviceWithRouteBuilder.adviceWith(camelContext, "getUsersRoute", a -> {
a.mockEndpointsAndSkip("bean:userService*");
});
user = new User();
user.setId(1);
user.setName("Jane");
mockUserService.returnReplyBody(constant(new User[] {user}));
}
#Test
public void callsRestWithMock() {
ResponseEntity<User[]> response = restTemplate.getForEntity("/rest/users", User[].class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
User[] s = response.getBody();
assertThat(s).contains(user);
}
#Test
public void callsDirectRouteWithMock() throws Exception {
User[] users = DefaultFluentProducerTemplate.on(camelContext)
.to("direct:getusers")
.request(User[].class);
assertThat(users).contains(user);
}
#Test
public void camelStarts() {
assertEquals(ServiceStatus.Started, camelContext.getStatus());
assertThat(camelContext.getRoutes()).hasSizeGreaterThan(0);
}
}
Assuming a RouteBuilder:
#Component
public class CamelRouter extends RouteBuilder {
#Value("${server.port}")
private int serverPort;
#Override
public void configure() throws Exception {
restConfiguration()
.contextPath("/rest")
.component("servlet")
.apiContextPath("/api-doc")
.port(serverPort)
.bindingMode(RestBindingMode.json)
.dataFormatProperty("prettyPrint", "true");
rest("/users")
.consumes("application/json")
.produces("application/json")
.get()
.outType(User[].class).to("direct:getusers");
from("direct:getusers").routeId("getUsersRoute")
.log("Get users")
.to("bean:userService?method=listUsers");
}
}
and application.yml:
camel:
component:
servlet:
mapping:
context-path: /rest/*
springboot:
name: MyCamel

Related

Autoconfigure ReactiveCrudRepos in integration tests

I'm having some difficulty in writing some Integration tests. I have something like so
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Service.class)
public class ServiceTest {
#Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();
#Autowired
Service Service;
#Before
public void setUp() {
PostgresqlConnectionFactory connectionFactory = new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder()
.host(postgres.getHost())
.port(postgres.getFirstMappedPort()) // optional, defaults to 5432
.username(postgres.getUsername())
.password(postgres.getPassword())
.database(postgres.getDatabaseName()) // optional
.build());
Resource resource = new ClassPathResource("sql.sql");
Mono<PostgresqlConnection> mono = connectionFactory.create();
mono.map(connection -> connection
.createStatement(Helpers.asString(resource))
.execute()).block();
}
#Test
public void test() {
Request Request = new Request();
request.setName("name");
Mono<Item> itemMono = Service.createNewHub(hubRequest);
Item item = itemMono.block();
Assert.assertEquals(1L, 1L);
}
}
And my Service.class looks like the below
#Service
public class Service {
private Repository repository;
public Service(Repository repository) {
this.repository = repository;
}
public Flux<Item> getAllItems() {
return repository.findAll();
}
}
And my repo
#Repository
public interface Repository extends ReactiveCrudRepository<Item, Integer> {
}
My error is the following
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.Repository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
While all of the code I have written in the application is able to be injected fine, when it comes to the ReactiveCrudRepos, I am not having any luck on getting their instantiated object. What do I need to do to have the implementations created and injected?
As long as you use #SpringBootTest(classes = Service.class), the other beans are not loaded into the application context. The annotation element class of the annotation is described as:
The component classes to use for loading an ApplicationContext.
Remove the element and use the #SpringBootApplication as is:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Service.class)
public class ServiceTest { /* code */ }
Remember, using this way is testing an application context completely different from what will be run on the production environment. It should be a last resort.
Moreover, avoid naming the interface Repository when there exists the Spring annotation #Repository itself. I would personally prefer:
#Repository
public interface ItemRepository extends ReactiveCrudRepository<Item, Integer> {
}

How do I resolve this bean defninition override?

I've upgraded from Spring Boot 1.5 to Spring Boot 2.1.8. I had some tests that were working but are now failing.
I also was using maven-surefire plugin at version 2.9 and it worked, but I upgraded that to 2.22.0 as well, if that matters.
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ElementController.class, secure = false)
#ContextConfiguration(classes = TestSite1Config.class)
public class ElementControllerSite1IT {
#Autowired
protected MockMvc mvc;
#MockBean
ElementService elementService;
#BeforeEach
public void setup() {
when(elementService.getElementTable( ... )) //skipping args for brevity
.thenReturn(new ElementTable());
}
#Configuration
public static class TestSite1Config {
#Bean
#Autowired
public ElementController elementController(final ElementService elementService) {
return new ElementController(elementService, new ElementControllerProperties(DeploymentLocation.SITE1));
}
#Test
public void failSite1ValidationWithoutId() throws Exception {
ElementParameters params = getParams(false);
mvc.perform(post("/element")
.contentType(JSON)
.andExpect(status().isBadRequest());
}
//more tests, but doesn't matter.
}
There's another class like above, but replace Site1 with Site2.
There is an ElementController & Service class as well.
I get this exception:
Caused by BeanDefinitionOverrideException: Invalid bean definition with name 'elementController' defined in class path resource [ui/v2/web/ElementControllerSite1IT$TestSite1Config.class]: Cannot register bean definition [Root bean: class [null]; ... defined in class path resource [ui/v2/web/ElementControllerSite1ITConfig.class] for bean 'elementController': There is already [Generic bean: class [ui.v2.web.ElementController]; .. defined in file [...ui/v2/web/ElementController.class]] bound.
I didn't write the tests, it's code that I've inherited, in a code base that I'm just getting spooled up on.
You could try #TestPropertySource(properties ="..." :
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ElementController.class, secure = false)
#ContextConfiguration(classes = TestSite1Config.class)
#TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=true", "local.server.port=7777"})
public class ElementControllerSite1IT {
...
}
Add spring.main.allow-bean-definition-overriding=true to application.properties
Got it working with this: (for anyone who stumbles upon this question)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#WebMvcTest
#ContextConfiguration(classes = {ElementController.class,TestSite1Config.class})
public class ElementControllerSite1IT {
#Autowired
private MockMvc mvc;
...
#Configruation
public static class TestSite1Config {
#Bean
#Primary
public ElementControllerProperties elementControllerProperties() { return ... }
}
...
}

problem of bean injection RabbitTemplate in Junit with spring-boot 2

I have small problems, injection of a bean when I run in spring-boot everything works well but with Junit. I have an example with RabbitTemplate
public class NettyServerRun {
#Autowired
private IDeviceClient deviceClientComponent;
#Autowired
private ServiceDeviceServer service;
#Autowired
private RabbitManager mqService;
private int port = 7650;
#PostConstruct
public void init() throws InterruptedException {
NioEventLoopGroup boosGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boosGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
final EventExecutorGroup group = new DefaultEventExecutorGroup(1500);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(workerGroup,new
RequestDecoderServer(deviceClientComponent));
pipeline.addLast(workerGroup,new ResponseEncoderServer());
pipeline.addLast(group,new AuthenticateHandler(service));
pipeline.addLast(group,new CommandResponseHandler(service));
pipeline.addLast(group,new DeviceDataHandler(mqService));
}
});
ChannelFuture f = bootstrap.bind(port).sync();
f.channel().closeFuture().sync();
}
#RunWith(SpringRunner.class)
#DataJpaTest
#Import(NettyServerRun.class)
#AutoConfigureTestDatabase(replace=Replace.ANY)
#ComponentScan("com.metier")
public abstract class DeviceServerTest {
#Autowired
protected TestEntityManager entityManager;
#Autowired
protected ServiceDeviceServer service;
#Autowired
protected TestRestTemplate template;
protected DeviceServerContext context;
protected Gson gson;
protected Nmea nmeaData;
#Value("${spring.profiles.active}")
private String activeProfile;
protected void persistList(List<AbstractEntity> list) {
list.forEach(entity -> entityManager.persist(entity));
}
public GpsCmdRsp buidGpsCmdRsp(Long gpsId) {
GpsCmdRsp reference = new GpsCmdRsp();
reference.setCommand("*TS01,188765,NAM#");
reference.setCompletedAt(buildHier());
reference.setGpsId(gpsId);
reference.setResponse("*TS01,353836057694499,013809281017,NAM:ODO50-
BLE#");
reference.setSuccess(false);
return reference;
}
public class DeviceDataHandlerTest extends DeviceServerTest {
#Autowired
private NettyServerRun nettyServerRun;
#Before
public void setUp() {
}
#Test
public void channelReadDeviceExistTest() {
String[] trame = {
"*TS01,351579056605817,003410140618,GPS:3;N46.758156;W71.134046;6;0;0.96,STT:c003;8001,MGR:957975,SAT:43;40;39#" };
EmbeddedChannel channel = new EmbeddedChannel(new DeviceDataHandler());
boolean ok = channel.writeInbound(trame);
assertThat(ok).isTrue();
}
}
Error log
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.amqp.rabbit.core.RabbitTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1654) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1213) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
... 44 common frames omitted ##
So,Like I said . if I run just with with Spring-Boot, the RabbitTemplate injection work fine . But , if a run with Junit. I try to add #SpringBootTest
java.lang.IllegalStateException: Configuration error: several statements found from #BootstrapWith for test class [com.AuthenticateHandlerTest]: [# org.springframework.test.context.BootstrapWith (value = class org.springframework.boot.tocon. test.autoconfigure.orm .jpa.DataJpaTestContextBootstrapper), # org.springframework.test.context.BootstrapWith (value = class org.springframework.boot.test.context.SpringBootTestContextBootstrapper)]
at org.springframework.test.context.BootstrapUtils.resolveExplicitTestContextBootstrapper (BootstrapUtils.java:166)
org.springframework.test.context.BootstrapUtils.resolveTestContextBootstrapper (BootstrapUtils.java:127).
Because of #DataJpaTest
I had the same problem and I had to mock the bean that uses RabbitTemplate.
Put this annotation at the top of your test's class name:
#MockBean(RabbitManager.class)
For anyone else, the class will be the one that contains the RabbitTemplate field, i.e.:
#MockBean(MyServiceContainingRabbitTemplate.class)
But that leads me to question why I had to do this. I would like to know :)
The #DataJpaTest annotation in your test uses to scan #Entity, repositories, EntityManager and other necessary beans for working with a database in tests, but this annotation does not load regular #Component beans into the ApplicationContext.
In your test case, you can use the #SpringBootTest annotation instead of the #DataJpaTest to load the entire application context.

Why spring boot does not load beans configuration in order?

When i try to run a spring boot project, it tolde me that it can not autowire some beans whitch are instanciated in a configuration classes.
I think that spring can not load those configuration classes in order.
The stack trace : no bean found the be autowired Ignite<Long,MyEntity> myEntityCache in MyDao
Here is the source :
The main class
#SpringBootApplication
// The beans in the IgniteConfig have to be loaded before dao, service, and Controller
#ComponentScan(basePackageClasses={IgniteConfig.class,AppConfig.class})
public class DemoIgnite {
public static void main(String[] args) {
SpringApplication.run(DemoIgnite .class, args);
}
}
Config Class 1
#Configuration
public class IgniteConfig {
#Bean
public SpringContext springContext() {
return new SpringContext();
}
#Bean
public Ignite igniteInstance(#Autowired SpringContext springContext) {
IgniteConfiguration cfg = new IgniteConfiguration();
cfg.setIgniteInstanceName("instance");
List<CacheConfiguration> ccDas = new ArrayList<>();
CacheConfiguration cch = new CacheConfiguration<>("myEntitycache");
cch.setCacheMode(CacheMode.REPLICATED);
cch.setIndexedTypes(Long.class, myEntity.class);
ccDas.add(cch);
cfg.setCacheConfiguration( ccDas.toArray(new CacheConfiguration[0]));
SpringCacheManager springCacheManager = new SpringCacheManager();
springCacheManager.setConfiguration(cfg);
return Ignition.start(cfg);
}
#Bean
public IgniteCache<Long, MyEntity> myEntityCache(#Autowired Ignite igniteInstance) {
return igniteInstance.cache("myEntitycache");
}
Config class 2
#Configuration
#ComponentScan({
"com.demo.repository",
"com.demo.service",
"com.demo.controller"
})
public class AppConfig {
}
Dao class
#Repository
public class MyDao{
#Autowired
private Ignite<Long,MyEntity> myEntityCache;
...
Service class:
#Service
public class MyService{
#Autowird
private MyDao dao;
...
Controller class:
#RestController
#RequestMapping
public class MyController{
#Autowired
private MyService service;
....
This means that you don't have a bean of Ignite<Long,MyEntity> type in your context. Moreover springContext bean seems redundant, it's not used by igniteInstance bean. As pointed out by moilejter it probably should be:
IgniteConfig
#Bean
public Ignite ignite() {
...
}
#Bean
public IgniteCache<Long, MyEntity> myEntityCache() {
return ignite().cache("myEntitycache");
}
MyDao
#Repository
public class MyDao {
#Autowired
private IgniteCache<Long, MyEntity> myEntityCache;
...
}
In principle Spring performs the bean setup in few phases as explained in chapter 1.3.2. Instantiating Beans docs:
Bean definition discovery - resources like #Configuration classes or XML files are scanned and bean signatures are collected.
Eager beans instantiation e.g. singletons - from the definitions collected in point 1 while resolving dependencies between definitions. That's why there is no explicit bean instantiation order as the process is driven from dependencies.
Lazy beans instantiation e.g. #Lazy annotated - when the context is already up, this beans will be constructed only when accessed from code.

Spring Boot Custom AutoConfiguration and Autowire

I am creating a custom AutoConfiguration for Spring Boot. One of the features I was attempting to create was to create one or more Beans dynamically and adding them to the ApplicationContext at runtime.
The problem I ran into was with Autowiring. My #SpringBootApplication class autowires those beans, and since they do not exist yet, autowire fails.
My first solution was to put #Lazy on the autowire, and that solved my problem.
However, I ran into something interesting. I added two beans that I was looking for into the AutoConfiguration code, and of course, it worked. By accident, I only removed one of the beans and re-ran my code. It worked.
#SpringBootApplication
public class SpringBootDemoApplication {
#Autowired
#Qualifier("some_name")
private MyClass myClass;
#Autowired
#Qualifier("another_name")
private MyClass anotherClass;
...
}
#Configuration
public class MyAutoConfigurationClass {
#Bean(name="some_class")
public MyClass myClass () {
return null;
}
}
So the short of it is this. If I defined only one of the beans in my autoconfiguration class, this seems to satisfy Autowired and it does not blow up and when I dynamically add my other beans, both beans are found.
The stipulation is that the Autowired bean that is first, must be the bean that is defined in my autoconfiguration class.
I am running the following:
Spring Boot Starter 1.5.7-RELEASE
Various Spring Framework 4.3.11-RELEASE
Is this a bug? Or is this the way Autowired is supposed to work?
#SpringBootApplication
public class SpringBootDemoApplication {
#Autowired
#Qualifier("myclass")
private MyClass myClass;
#Autowired
#Qualifier("anotherMyClass")
private MyClass anotherMyClass;
...
}
#Configuration
public class MyAutoConfiguration {
private ConfigurableApplicationContext applicationContext;
private final BeanFactory beanFactory;
#Autowired
private MyClassFactory myClassFactory;
public MyAutoConfiguration(ApplicationContext applicationContext, BeanFactory beanFactory) {
this.beanFactory = beanFactory;
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
#PostConstruct
public void init() throws IOException, SQLException {
this.myClassFactory.create(this.applicationContext);
}
// without this #Bean definition SpringBoot will recieve the following error and stop
// AnnotationConfigEmbeddedWebApplicationContext - Exception encountered during context initialization
#Bean(name="myClass")
public DataSource anyNameWillDoItDoesntMatter() {
return null;
};
}
#Component
class MyClassFactory {
public void create(ConfigurableApplicationContext applicationContext) {
applicationContext.getBeanFactory().registerSingleton(name, value);
}
}
So is this expected behavior of #Autowired?

Resources