I am seeing following error while running the unit testing my spring integration code. And also i was not sure how to mock my service and other dependencies while using mockIntegrationContext.
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'orderInputEndPoint' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:686)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1210)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
Main program code
#EnableIntegration
public class OrderPersistFlow {
#Autowired
private ActiveMQConnectionFactory activeMQConnectionFactory;
#Autowired
private OrderTransformer orderTransformer;
#Autowired
private OrderService orderService;
#Bean
public IntegrationFlow persistFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(activeMQConnectionFactory)
.id("orderInputEndPoint")
.destination("order.queue")
.jmsMessageConverter(new MarshallingMessageConverter(jaxbMarshaller())))
.filter(OrderVO.class, p -> p.getOrderStatus().equals("OPEN")
.transform(orderTransformer)
.handle(orderService, "save")
.get();
}
}
Test code
RunWith(SpringRunner.class)
#SpringIntegrationTest(noAutoStartup = "orderInputEndPoint")
public class OrderPersistFlowTest {
#Autowired
private MockIntegrationContext mockIntegrationContext;
#Test
public void persistFlowTest(){
OrderVO orderVO = new OrderVO();
orderVO.setId("1234");
orderVO.setName("TestOrder");
orderVO.setDescription("order desc");
MessageSource<OrderVO> messageSource = () -> new GenericMessage<>(orderVO);
this.mockIntegrationContext.substituteMessageSourceFor("orderInputEndPoint", messageSource);
Message<?> receive = messageSource.receive();
}
}
I don't see a #ContextConfiguration(classes = OrderPersistFlow.class) on your test class: https://docs.spring.io/spring/docs/5.0.6.RELEASE/spring-framework-reference/testing.html#integration-testing-annotations-spring
Fully not clear what you are going to test then if there is no application context to load...
Related
I'm trying to use mockito to replace the method call in SpringBatch. The code is greatly simplified and removed unnecessary to reduce it, if something is missing, write, I will add.
Spring Batch Settings File
#Configuration
#EnableBatchProcessing
public class ListBatchConfig {
#Bean
public Job jobListBath(JobBuilderFactory jobBuilderFactory,
StepBuilderFactory stepBuilderFactory,
ItemReader<Student> itemReaderListBath,
ItemProcessor<Student, Marksheet> processorListBath,
ItemWriter<Marksheet> itemWriterListBath
) {
Step step = stepBuilderFactory.get("List-load")
.<Student, Marksheet>chunk(3)
.reader(itemReaderListBath)
.processor(processorListBath)
.writer(itemWriterListBath)
.build();
return jobBuilderFactory.get("L-Load")
.incrementer(new RunIdIncrementer())
.start(step)
.build();
}
#Bean
public ItemReader<Emaill> itemReaderListBath() {
return new ListItemReader();
}
...
The ItemReader is described in a separate class List Item Reader
#Component
#Slf4j
public class ListItemReader implements ItemReader<Student> {
#Autowired
private CalService calService ;
DataAtributes dataAtributes;
...
#Override
public Student read() {
//Тут вызывается метод сервиса, который необходимо подменить
dataAtributes = new DataAtributes(1,"test");
Integer val = calService.addAttr(dataAtributes);
...
}
Here is the service itself
#Service
public class CalService {
public int addI(int input1) {
return 0;
}
public Integer addAttr(DataAtributes attr) {
return 0;
}
}
in this service, the Job starts
#Service
public class StartJob {
#Autowired
JobLauncher jobLauncher;
#Autowired
Job jobDeveloper;
public void launchJob() throws Exception {
...
JobExecution jobExecution = jobLauncher.run(jobDeveloper, params);
and the testing class itself
#SpringBootTest
public class CaclServisTest {
#Mock
CalService calcService;
#InjectMocks
StartJob
#Autowired
StartJob startJob;
#Test
void add() {
DataAtributes dataAtributes = new DataAtributes(1,"test");
when(calcService.addAttr(dataAtributes)).thenReturn(57);
startJob.launchJob();
}
}
As a result, the native method is called, not the substituted one.
I do not understand what service should be installed #InjectMocks
Your #Mock field is not available for in Spring context.
In particular: #Autowire StartJob startJob uses CalService from Spring context, not your mock.
To replace a bean in Spring context, use #MockBean instead:
#SpringBootTest
public class CaclServisTest {
#MockBean
CalService calcService;
#Autowired
StartJob startJob;
#Test
void add() {
DataAtributes dataAtributes = new DataAtributes(1,"test");
when(calcService.addAttr(dataAtributes)).thenReturn(57);
startJob.launchJob();
}
}
How to run the integrationFlow in the below JUnit class? Currently there comes the exception
java.lang.AssertionError: Further request(s) expected leaving 1 unsatisfied expectation(s). 0 request(s) executed.
because the integration flow is not started.
The JUnit class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#DirtiesContext
public class FlowTest {
private final RestTemplate restTemplate = new RestTemplate();
private MockRestServiceServer mockServer;
#Before
public void setup() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void test() {
mockServer.expect(requestTo("http://localhost:8080/data"));
final IntegrationFlow integrationFlow = f -> f
.handle(Http.outboundGateway("http://localhost:8080/data", restTemplate).httpMethod(HttpMethod.GET)
.expectedResponseType(String.class));
mockServer.verify();
}
}
You can't just define a flow like that in a test method; the framework has to do a bunch of assembly behind the scenes.
Define the flow as a #Bean in a test #Configuration class.
I'm using Spring Boot and Spring Integration Java DSL in my #Configuration class. One of the flows is using DelayHandler with MessageStore, by means of .delay(String groupId, String expression, Consumer endpointConfigurer):
#Bean
public IntegrationFlow errorFlow() {
return IntegrationFlows.from(errorChannel())
...
.delay(...)
...
.get();
}
I was hoping to utilize the reschedulePersistedMessages() functionality of DelayHandler, but I found out the onApplicationEvent(ContextRefreshedEvent event) which invokes it is actually never invoked (?)
I'm not sure, but I suspect this is due to the fact DelayHandler is not registered as a Bean, so registerListeners() in AbstractApplicationContext is not able to automatically register DelayHandler (and registration of non-bean listeners via ApplicationEventMulticaster.addApplicationListener(ApplicationListener listener) is not done for DelayHandler.
Currently I'm using a rather ugly workaround of registering my own listener Bean into which I inject the integration flow Bean, and then invoking the onApplicationEvent() manually after locating the DelayHandler:
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Set<Object> integrationComponents = errorFlow.getIntegrationComponents();
for (Object component : integrationComponents) {
if (component instanceof DelayerEndpointSpec) {
Tuple2<ConsumerEndpointFactoryBean, DelayHandler> tuple2 = ((DelayerEndpointSpec) component).get();
tuple2.getT2().onApplicationEvent(event);
return;
}
}
}
Well, yes. This test-case confirm the issue:
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class DelayerTests {
private static MessageGroupStore messageGroupStore = new SimpleMessageStore();
private static String GROUP_ID = "testGroup";
#BeforeClass
public static void setup() {
messageGroupStore.addMessageToGroup(GROUP_ID, new GenericMessage<>("foo"));
}
#Autowired
private PollableChannel results;
#Test
public void testDelayRescheduling() {
Message<?> receive = this.results.receive(10000);
assertNotNull(receive);
assertEquals("foo", receive.getPayload());
assertEquals(1, messageGroupStore.getMessageGroupCount());
assertEquals(0, messageGroupStore.getMessageCountForAllMessageGroups());
}
#Configuration
#EnableIntegration
public static class ContextConfiguration {
#Bean
public IntegrationFlow delayFlow() {
return flow ->
flow.delay(GROUP_ID, (String) null,
e -> e.messageStore(messageGroupStore)
.id("delayer"))
.channel(c -> c.queue("results"));
}
}
}
Here we go: https://github.com/spring-projects/spring-integration-java-dsl/issues/59.
As a workaround we can do this in our #Configuration:
#Autowired
private ApplicationEventMulticaster multicaster;
#PostConstruct
public void setup() {
this.multicaster.addApplicationListenerBean("delayer.handler");
}
Pay attention to the beanName to register. This is exactly that .id("delayer") from our flow definition plus the .handler suffix for the DelayHandler bean definition.
I would like to intercept all spring integration gateways via AOP.
Is it possible to do that? If not what might be best way to do log input object coming to gateway?
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class AdviceExample {
#Autowired
private TestGateway testGateway;
#Test
public void testIt() {
System.out.println(this.testGateway.testIt("foo"));
}
#MessagingGateway
public interface TestGateway {
#Gateway(requestChannel = "testChannel")
#CustomAnnotation
String testIt(String payload);
}
#Configuration
#EnableIntegration
#IntegrationComponentScan
#EnableMessageHistory
#EnableAspectJAutoProxy
public static class ContextConfiguration {
LoggingHandler logger = new LoggingHandler(LoggingHandler.Level.INFO.name());
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.transform("payload.toUpperCase()")
.channel("testChannel")
.transform("payload.concat(' Manoj')")
.channel("testChannel")
.handle(logger)
.get();
}
#Bean
public GatewayAdvice gtwyAdvice(){
return new GatewayAdvice();
}
}
#Retention(value = RetentionPolicy.RUNTIME)
#Target(value = ElementType.METHOD)
#Inherited
public #interface CustomAnnotation{
}
#Aspect
public static class GatewayAdvice {
#Before("execution(* advice.AdviceExample.TestGateway.testIt(*))")
public void beforeAdvice() {
System.out.println("Before advice called...");
}
#Before("#annotation(advice.AdviceExample.CustomAnnotation)")
public void beforeAnnotationAdvice() {
System.out.println("Before annotation advice called...");
}
}
}
Yes, you can do that. Take a look to the standard Spring AOP Framework. Since all those #Gateway are beans in the end you can add for them any Advice by their bean names and for the specific method, if that. For example we often suggest to use #Transactional on gateway's methods. And this is exactly a sample "how to use AOP on integration gateway".
We are planning to migrate our code from Spring integration XML to DSL. In XML Version, we are using channel name pattern to do tracing.
For Eg: If channel name has *_EL_*, we intercept the channel and do some logging.
How to do this kind or more simpler in Java dsl.
The #GlobalChannelInterceptor is for you. And it is a part of Spring Integration Core.
So, you must do something like this:
#Bean
public MessageChannel bar() {
return new DirectChannel();
}
#Bean
#GlobalChannelInterceptor(patterns = "*_EL_*")
public WireTap baz() {
return new WireTap(this.bar());
}
I mean specify the ChannelInterceptor #Bean and mark it with that annotation to make pattern-based interceptions.
UPDATE
The sample test-case which demonstrate the work for #GlobalChannelInterceptor for the auto-created channel from DSL flows:
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class SO31573744Tests {
#Autowired
private TestGateway testGateway;
#Autowired
private PollableChannel intercepted;
#Test
public void testIt() {
this.testGateway.testIt("foo");
Message<?> receive = this.intercepted.receive(1000);
assertNotNull(receive);
assertEquals("foo", receive.getPayload());
}
#MessagingGateway
public interface TestGateway {
#Gateway(requestChannel = "testChannel")
void testIt(String payload);
}
#Configuration
#EnableIntegration
#IntegrationComponentScan
public static class ContextConfiguration {
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.channel("nullChannel")
.get();
}
#Bean
public PollableChannel intercepted() {
return new QueueChannel();
}
#Bean
#GlobalChannelInterceptor(patterns = "*Ch*")
public WireTap wireTap() {
return new WireTap(intercepted());
}
}
}