I can't use mockito to replace the method call in SpringBatch - mockito

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

Related

Springboot mockito mock a field

I want to mock the TreeMap in Service class, I tried to mock using TreeMap<String, String> requestsMap = Mockito.mock(TreeMap.class); But the map is getting null at runtime.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class ServiceTest {
#InjectMocks
private Service service;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test
public void test() {
TreeMap<String, String> requestsMap = Mockito.mock(TreeMap.class);
}
}
#Service
public class Service {
private TreeMap<String, String> requestsMap = null;
}
Your Service class doesn't actually do anything but declare and initialize the requestMap to null. Also, your test method doesn't do anything other than declare a mock requestMap.
In addition to the code you have, write a method in the Service that uses the request map and just declare/autowire the requestMap. In your test, use the #Mock annotation on a declaration of your mock requestMap. Finally, use the mocked requestMap in your test.
For example:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class ServiceTest {
#Mock
TreeMap<String, String> requestsMap;
#InjectMocks
private Service service;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test
public void test() {
when(requestMap.get("foo")).thenReturn("bar");
String result = service.doSomething();
assertEquals(result, "bar");
}
}
#Service
public class Service {
#Autowired
private TreeMap<String, String> requestsMap;
public String doSomething() {
return requestMap.get("foo");
}
}

#SpringIntegrationTest annotation does not load context as expected

Normally, when I use #SpringBootTest I get the full context of beans. I can the #Autowire all kinds of beans that are available after the application has started.
Now, in the scope of spring-integration-test libary, the #SpringIntegrationTest does not do this.
As the testing module promises, you can use
#Autowired
private MockIntegrationContext mockIntegrationContext;
However, after inspecting the bean map on that instance, I found out there are no beans!
Example test:
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringIntegrationTest
public class AppTest {
#Autowired
private MockIntegrationContext mockIntegrationContext;
#Test
public void contextLoads() {
// put breakpoint to inspect field
System.out.println(mockIntegrationContext);
}
}
When I however run the following code, I get a complete context:
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest
public class App2Test {
#Autowired
private ListableBeanFactory beanFactory;
#Test
public void contextLoads() {
Assert.isTrue(beanFactory.getBeanDefinitionCount() > 0)
}
}
Why is that? How can I achieve a similar result with spring-integration-test?
Reading materials: https://docs.spring.io/spring-integration/docs/current/reference/html/testing.html
They are independent annotations; you need both.
EDIT
This works fine for me:
#RunWith(SpringRunner.class)
#SpringBootTest
#SpringIntegrationTest
public class So52297757ApplicationTests {
#Autowired
private MockIntegrationContext mockIntegrationContext;
#Autowired
private String foo;
#Test
public void contextLoads() {
System.out.println(foo);
System.out.println(mockIntegrationContext);
}
}
and
#SpringBootApplication
public class So52297757Application {
public static void main(String[] args) {
SpringApplication.run(So52297757Application.class, args);
}
#Bean
public String foo() {
return "foo";
}
}
and
foo
org.springframework.integration.test.context.MockIntegrationContext#1de5f0ef

Error while unit testing spring integration dsl

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...

spring integration sftp error: Dispatcher has no subscribers

I'm trying to use the outbound gateway to download files from sftp server,
my config:
#IntegrationComponentScan
#EnableIntegration
#Configuration
public class FtpConfig {
#Bean(name = "myGateway")
#ServiceActivator(inputChannel = "sftpChannel")
public MessageHandler handlerLs() {
SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "mget", "payload");
sftpOutboundGateway.setLocalDirectory(new File("/Users/xxx/Documents/"));
return sftpOutboundGateway;
}
#MessagingGateway
public interface OutboundGatewayOption {
#Gateway(requestChannel = "sftpChannel")
List<File> mget(String dir);
}
#Bean
public MessageChannel sftpChannel() {
return new DirectChannel();
}
}
and the execute bean:
#Service
public class DownloadService implements InitializingBean{
#Autowired
FtpConfig.OutboundGatewayOption gatewayOption;
#Override
public void afterPropertiesSet() throws Exception {
List<File> files = gatewayOption.mget("/sftp/server/path");
}
}
and I got this exception:org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.sftpChannel'.;
Qestion :how can I add the 'subscribers' ?
You can't perform messaging operator in the afterPropertiesSet(). That's too early : some beans might not be initialized yet. And that exception confirms the problem.
You have to implement SmartLifecicle instead and do the same in the start().

onApplicationEvent() is never invoked on DelayHandler

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.

Resources