MockIntegrationMessage not working when context cached

gnzlrm :

I'm working on a project using Spring Integration 5.1.7.RELEASE, Spring Boot 2.1.7.RELEASE and Spring Integration Test 5.0.11.RELEASE.

As part of my tests, I'm trying to unit test an IntegrationFlow that performs calls to different MessageHandlers, so I'm mocking them using the MockIntegrationContext from Spring Integration Test.

My code is having intermittent issues on our Jenkins server that seems to be related with the context being cached. I've an integration test (@SpringBootTest) with no mocks and a test class specifically for the IntegrationFlow where MessageHandlers are mocked.

If the Integration Test runs before and the context is cached, the setups performed by the MockIntegrationContext seem to have no effect and the underlying service-activator is called, even when the logs show that it was unsubscribed and the mock was subscribed to the channel instead.

Here's the definition of the poller and the handle endpoint:

@Configuration
public class FlowConfig {

  @Bean
  public IntegrationFlow fileFlow() {
          return IntegrationFlows.from(
                  Files.inboundAdapter(new File(fileDir)).autoCreateDirectory(true).preventDuplicates(false),
                      e -> e.poller(Pollers.fixedDelay(pollingFrequency)).id("fileInboundSourceEndpoint").get())
                  ...
                  .handle(fileParseHandler, e -> e.id("fileParseHandlerEndpoint")) 
                  ...
                  .get()
  }
}

Here's the test setup:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = FlowConfig.class)
@SpringIntegrationTest
@EnableIntegration
public class FlowConfigTest {

    @Autowired
    MockIntegrationContext mockIntegrationCtx;

    @Test
    public void test_fileFlow() throws IOException {
        mockIntegrationCtx.substituteMessageHandlerFor("fileParseHandlerEndpoint",
                    MockIntegration.mockMessageHandler().handleNextAndReply(m -> m));
        new File(fileDir + "test").createNewFile();
    }
}

And this are two different set of logs from one unsuccessful and one successful test execution, respectively:

2019-09-09 15:17:02.059  INFO 14462 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : Removing {service-activator:fileParseHandlerEndpoint} as a subscriber to the 'fileFlow.channel#2' channel
2019-09-09 15:17:02.059  INFO 14462 --- [           main] o.s.integration.channel.DirectChannel    : Channel 'org.springframework.context.support.GenericApplicationContext@51f95f0d.fileFlow.channel#2' has 0 subscriber(s).
2019-09-09 15:17:02.059  INFO 14462 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : stopped fileParseHandlerEndpoint
2019-09-09 15:17:02.062  INFO 14462 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : Adding {message-handler:fileParseHandlerEndpoint} as a subscriber to the 'fileFlow.channel#2' channel
2019-09-09 15:17:02.062  INFO 14462 --- [           main] o.s.integration.channel.DirectChannel    : Channel 'org.springframework.context.support.GenericApplicationContext@51f95f0d.fileFlow.channel#2' has 1 subscriber(s).
2019-09-09 15:17:02.062  INFO 14462 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : started fileParseHandlerEndpoint
...
2019-09-09 15:17:03.731 DEBUG 14462 --- [   scheduling-1] o.s.i.file.FileReadingMessageSource      : Added to queue: [/tmp/file]
2019-09-09 15:17:03.732 DEBUG 14462 --- [   scheduling-1] o.s.i.e.SourcePollingChannelAdapter      : Poll resulted in Message: GenericMessage [payload=/tmp/file, headers={file_originalFile=/tmp/file, id=4323b036-4edd-019e-2c61-6917c7970767, file_name=file, file_relativePath=file, timestamp=1568056623732}]
...
2019-09-09 15:17:03.732 DEBUG 14462 --- [   scheduling-1] o.s.integration.channel.DirectChannel    : preSend on channel 'fileFlow.channel#2', message: GenericMessage [payload=/tmp/file, headers={file_originalFile=/tmp/file, errorChannel=error_channel, id=af997a89-40d6-9024-65fc-7ab3883c329b, file_name=file, file_relativePath=file, timestamp=1568056623732}]
2019-09-09 15:17:03.732 DEBUG 14462 --- [   scheduling-1] o.s.i.handler.ServiceActivatingHandler   : ServiceActivator for [org.springframework.integration.handler.MethodInvokingMessageProcessor@7b4acdc2] (fileParseHandlerEndpoint) received message: GenericMessage [payload=/tmp/file, headers={file_originalFile=/tmp/file, errorChannel=error_channel, id=af997a89-40d6-9024-65fc-7ab3883c329b, file_name=file, file_relativePath=file, timestamp=1568056623732}]
[Fatal Error] file:1:1: Premature end of file.
13:41:05.126 [main] INFO  o.s.i.endpoint.EventDrivenConsumer - Removing {service-activator:fileParseHandlerEndpoint} as a subscriber to the 'fileFlow.channel#2' channel
13:41:05.126 [main] INFO  o.s.i.channel.DirectChannel - Channel 'org.springframework.context.support.GenericApplicationContext@6fb365ed.fileFlow.channel#2' has 0 subscriber(s).
13:41:05.126 [main] INFO  o.s.i.endpoint.EventDrivenConsumer - stopped fileParseHandlerEndpoint
13:41:05.128 [main] INFO  o.s.i.endpoint.EventDrivenConsumer - Adding {message-handler:fileParseHandlerEndpoint} as a subscriber to the 'fileFlow.channel#2' channel
13:41:05.128 [main] INFO  o.s.i.channel.DirectChannel - Channel 'org.springframework.context.support.GenericApplicationContext@6fb365ed.fileFlow.channel#2' has 1 subscriber(s).
13:41:05.128 [main] INFO  o.s.i.endpoint.EventDrivenConsumer - started fileParseHandlerEndpoint
...
13:41:06.775 [task-scheduler-1] DEBUG o.s.i.file.FileReadingMessageSource - Added to queue: [/tmp/file]
13:41:06.775 [task-scheduler-1] DEBUG o.s.i.e.SourcePollingChannelAdapter - Poll resulted in Message: GenericMessage [payload=/tmp/file, headers={file_originalFile=/tmp/file, id=3bb52d11-49ad-3ea8-eb67-7e6da721022a, file_name=file, file_relativePath=file, timestamp=1568050866775}]
...
13:41:06.788 [task-scheduler-1] DEBUG o.s.i.channel.DirectChannel - preSend on channel 'fileFlow.channel#2', message: GenericMessage [payload=/tmp/file, headers={file_originalFile=/tmp/file, errorChannel=error_channel, id=ec6b0213-8109-ae43-ac27-d17b1bbad8aa, file_name=file, file_relativePath=file, timestamp=1568050866777}]
13:41:06.789 [task-scheduler-1] DEBUG o.s.i.test.mock.MockMessageHandler - org.springframework.integration.test.mock.MockMessageHandler$MockitoMock$1634676946@76fb98a2 received message: GenericMessage [payload=/tmp/file, headers={file_originalFile=/tmp/file, errorChannel=error_channel, id=ec6b0213-8109-ae43-ac27-d17b1bbad8aa, file_name=file, file_relativePath=file, timestamp=1568050866777}]

I know there's probably a combination of @DirtiesContext that should fix this problem, but I'm still struggling to understand why MockIntegrationContext isn't able to mock MessageHandlers from the cached context.

Is there any way that I can fix this issue without using @DirtiesContext?

Artem Bilan :

First of all when you use Spring Boot, you need to rely on its version management. Also when you use Spring Integration as is, consider to use spring-integration-bom for imports. This way yo again don't need to specify version for individual modules.

My point is that you have different versions for spring-integration-core and spring-integration-test. They have to be same. Otherwise you may end up with a mess at runtime.

Now about the subject.

I don't think it is a caching problem.

You have a race condition between application context start for this test suite and that mockIntegrationCtx.substituteMessageHandlerFor() in the target test method.

The problem comes from the fact that you start flow from the e.poller(Pollers.fixedDelay(pollingFrequency). This one is handled by the TaskScheduler on background and produces messages independently of the main thread for your test suite. That's why you observe unexpected behavior.

You can overcome the problem with something like autoStartup manipulation. The @SpringIntegrationTest has this one for you:

/**
 * Specify a simple matching patterns ("xxx*", "*xxx", "*xxx*" or "xxx*yyy") for
 * {@link org.springframework.integration.endpoint.AbstractEndpoint}
 * bean names to mark them as {@code autoStartup = false}
 * during context initialization.
 * @return the endpoints name patterns to stop during context initialization
 * @see IntegrationEndpointsInitializer
 * @see org.springframework.util.PatternMatchUtils
 */
String[] noAutoStartup() default {};

So, you do like this:

@SpringIntegrationTest(noAutoStartup = "fileInboundSourceEndpoint")

Then in the test you autowire a channel adapter:

@Autowired private SourcePollingChannelAdapter fileInboundSourceEndpoint;

And after substituteMessageHandlerFor() you start it:

this.fileInboundSourceEndpoint.start();

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=153533&siteId=1