Search code examples
javaspring-integrationspring-boot-test

Unit Test Spring Integration flow DSL


I am trying to unit test a simple flow where it is checking for existence of a file and then do some additional task.

IntegrationFlow

@Bean
public IntegrationFlow initiateAlarmAck() {
    return IntegrationFlows.from("processAckAlarmInputChannel")
            .handle((payload, headers) ->  {
                LOG.info("Received initiate ack alarm request at " + payload);
                File watermarkFile = getWatermarkFile();
                if(watermarkFile.isFile()){
                    LOG.info("Watermark File exists");
                    return true;
                }else{
                    LOG.info("File does not exists");
                    return false;
                }
            })
            .<Boolean, String>route(p -> fileRouterFlow(p))
            .get();
}
File getWatermarkFile(){
    return new File(eventWatermarkFile);
}

@Router
public String fileRouterFlow(boolean fileExits){
    if(fileExits)
        return "fileFoundChannel";
    else
        return "fileNotFoundChannel";
}

There is another integration flow which pick a message from fileNotFoundChannel and does additional processing. I do not want to unit test this portion. How do I stop my test to not do further and stop after putting a message on fileNotFoundChannel?

@Bean
public IntegrationFlow fileNotFoundFlow() {
    return IntegrationFlows.from("fileNotFoundChannel")
            .handle((payload, headers) ->  {
                LOG.info("File Not Found");
                return payload;
            })
            .handle(this::getLatestAlarmEvent)
            .handle(this::setWaterMarkEventInFile)
            .channel("fileFoundChannel")
            .get();
}

Unit Test Class

@RunWith(SpringRunner.class)
@Import(AcknowledgeAlarmEventFlow.class)
@ContextConfiguration(classes = {AlarmAPIApplication.class})
@PropertySource("classpath:application.properties ")
public class AcknowledgeAlarmEventFlowTest {


    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    @Qualifier("processAckAlarmInputChannel")
    DirectChannel processAckAlarmInputChannel;

    @Autowired
    @Qualifier("fileNotFoundChannel")
    DirectChannel fileNotFoundChannel;

    @Autowired
    @Qualifier("fileFoundChannel")
    DirectChannel fileFoundChannel;

    @Mock
    File mockFile;

    @Test
    public void initiateAlarmAck_noFileFound_verifyMessageOnfileNotFoundChannel(){


        AcknowledgeAlarmEventFlow.ProcessAcknowledgeAlarmGateway gateway = applicationContext.getBean(AcknowledgeAlarmEventFlow.ProcessAcknowledgeAlarmGateway.class);
        gateway.initiateAcknowledgeAlarm();

        processAckAlarmInputChannel.send(MessageBuilder.withPayload(new Date()).build());
        MessageHandler mockMessageHandler = mock(MessageHandler.class);

        fileNotFoundChannel.subscribe(mockMessageHandler);
        verify(mockMessageHandler).handleMessage(any());
    }
}

Thanks in advance


Solution

  • That is exactly scenario what we are doing now with the MockMessageHandler implementation.

    Looks like you go right way with mocking to prevent further action in that fileNotFoundFlow, but miss some simple tricks:

    You have to stop() the real .handle((payload, headers) ) endpoint on that fileNotFoundChannel. That way it will unsubscribe from the channel and won't consume messages any more. For this purpose I suggest to do:

    return IntegrationFlows.from("fileNotFoundChannel")
    .handle((payload, headers) ->  {
      LOG.info("File Not Found");
      return payload;
    }, e -> e.id("fileNotFoundEndpoint"))
    

    And in the test class

    @Autowired
    @Qualifier("fileNotFoundEndpoint")
    AbstractEndpoint fileNotFoundEndpoint;
     ...
    
    @Test
    public void initiateAlarmAck_noFileFound_verifyMessageOnfileNotFoundChannel(){
      this.fileNotFoundEndpoint.stop();
    
      MessageHandler mockMessageHandler = mock(MessageHandler.class);
    
      fileNotFoundChannel.subscribe(mockMessageHandler);
    
    
      AcknowledgeAlarmEventFlow.ProcessAcknowledgeAlarmGateway gateway = applicationContext.getBean(AcknowledgeAlarmEventFlow.ProcessAcknowledgeAlarmGateway.class);
      gateway.initiateAcknowledgeAlarm();
    
      processAckAlarmInputChannel.send(MessageBuilder.withPayload(new Date()).build());
      verify(mockMessageHandler).handleMessage(any());
    }
    

    Pay, please, attention, how I have moved mocking and subscribing before sending message to the channel.

    With the new MockIntegrationContext features the Framework will take care about that stuff for you. But yeah... As with any unit test, the mocks must be prepared before interaction.

    UPDATE

    Working sample:

    @RunWith(SpringRunner.class)
    @ContextConfiguration
    public class MockMessageHandlerTests {
    
    @Autowired
    private SubscribableChannel fileNotFoundChannel;
    
    @Autowired
    private AbstractEndpoint fileNotFoundEndpoint;
    
    @Test
    @SuppressWarnings("unchecked")
    public void testMockMessageHandler() {
        this.fileNotFoundEndpoint.stop();
    
        MessageHandler mockMessageHandler = mock(MessageHandler.class);
    
        this.fileNotFoundChannel.subscribe(mockMessageHandler);
    
        GenericMessage<String> message = new GenericMessage<>("test");
        this.fileNotFoundChannel.send(message);
    
        ArgumentCaptor<Message<?>> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
    
        verify(mockMessageHandler).handleMessage(messageArgumentCaptor.capture());
    
        assertSame(message, messageArgumentCaptor.getValue());
    }
    
    @Configuration
    @EnableIntegration
    public static class Config {
    
        @Bean
        public IntegrationFlow fileNotFoundFlow() {
            return IntegrationFlows.from("fileNotFoundChannel")
            .<Object>handle((payload, headers) -> {
                System.out.println(payload);
                return payload;
            }, e -> e.id("fileNotFoundEndpoint"))
            .channel("fileFoundChannel")
            .get();
        }
    
    }
    
    }