2

I am trying to mock in and out paths of Camel Routes but I don't know how to provide mock in and out path.Please help me to fix this.Thanks in advance.

application.properties

inputFilePath = src/main/resources/in
outputFilePath = src/main/resources/out

application-test.properties

inputFilePath = src/test/java/in
outputFilePath = src/test/java/out

Router and Processor:

@Component
public class FileLineByLineRouter extends RouteBuilder {

    @Value("${inputFilePath}")
    private String inputFilePath;

    @Value("${outputFilePath}")
    private String outputFilePath;

    @Override
    public void configure() throws Exception {
        from("file://" + inputFilePath + "?delete=true").routeId("FileLineByLineRoute").marshal().string("UTF-8")
                .split(body().tokenize("\n")).streaming().process(getFileParsingProcessor())
                .to("file://" + outputFilePath + "?fileExist=Append").end();
    }

    @Bean
    public Processor getFileParsingProcessor() {

        return new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                String order = exchange.getIn().getBody(String.class);
                order = order + ": " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss S").format(new Date()) + "\n";
                exchange.getIn().setBody(order);
            }
        };
    }
}

Junit Testing Code:

    @RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class })
@SpringBootTest(classes = FileLineByLineRouter.class)
@ActiveProfiles("test")
@EnableAutoConfiguration
public class FileLineByLineRouterTest2 extends CamelTestSupport {

    @Autowired
    protected CamelContext camelContext;

    @Test
    public void test() throws Exception {
        camelContext.start();
        Thread.sleep(2000);
        File outDir = new File("src/test/java/out");
        System.out.println(outDir.getAbsolutePath());
        assertTrue(outDir.isDirectory());
        assertTrue(outDir.listFiles().length != 0);
    }    
}

Logs:

114  SpringCamelContext      : Route: FileLineByLineRoute started and consuming from: file://src/test/java/in?delete=true
116  SpringCamelContext      : Total 1 routes, of which 1 are started.
122  SpringCamelContext      : Apache Camel 2.19.1 (CamelContext: camel-1) started in 0.582 seconds
138  FileLineByLineRouterTest2     : Started FileLineByLineRouterTest2 in 10.064 seconds (JVM running for 12.063)
179  FileLineByLineRouterTest2     : ********************************************************************************
180  FileLineByLineRouterTest2     : Testing: test(FileLineByLineRouterTest2)
180  FileLineByLineRouterTest2     : ********************************************************************************
222  o.apache.camel.impl.DefaultCamelContext  : Apache Camel 2.19.1 (CamelContext: camel-2) is starting
223  o.a.c.m.DefaultManagementStrategy        : JMX is disabled
238  o.a.c.i.converter.DefaultTypeConverter   : Loaded 193 type converters
239  o.apache.camel.impl.DefaultCamelContext  : StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
239  o.apache.camel.impl.DefaultCamelContext  : Total 0 routes, of which 0 are started.
239  o.apache.camel.impl.DefaultCamelContext  : Apache Camel 2.19.1 (CamelContext: camel-2) started in 0.017 seconds
239  SpringCamelContext      : Apache Camel 2.19.1 (CamelContext: camel-1) is starting
239  SpringCamelContext      : Total 1 routes, of which 1 are started.
239  SpringCamelContext      : Apache Camel 2.19.1 (CamelContext: camel-1) started in 0.000 seconds
C:\Users\workspace\CamelProject\src\test\java\out
241  FileLineByLineRouterTest2     : ********************************************************************************
241  FileLineByLineRouterTest2     : Testing done: test(FileLineByLineRouterTest2)
241  FileLineByLineRouterTest2     : Took: 0.002 seconds (2 millis)
241  FileLineByLineRouterTest2     : ********************************************************************************
242  o.apache.camel.impl.DefaultCamelContext  : Apache Camel 2.19.1 (CamelContext: camel-2) is shutting down
314  o.apache.camel.impl.DefaultCamelContext  : Apache Camel 2.19.1 (CamelContext: camel-2) uptime 0.092 seconds
318  o.apache.camel.impl.DefaultCamelContext  : Apache Camel 2.19.1 (CamelContext: camel-2) is shutdown in 0.071 seconds
336   o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@394df057: startup date [Mon Jan 08 17:32:43 IST 2018]; root of context hierarchy
344   o.a.camel.spring.SpringCamelContext      : Apache Camel 2.19.1 (CamelContext: camel-1) is shutting down
346   o.a.camel.impl.DefaultShutdownStrategy   : Starting to graceful shutdown 1 routes (timeout 300 seconds)
356  INFO 19900 --- [ - ShutdownTask] o.a.camel.impl.DefaultShutdownStrategy   : Route: FileLineByLineRoute shutdown complete, was consuming from: file://src/test/java/in?delete=true
356   o.a.camel.impl.DefaultShutdownStrategy   : Graceful shutdown of 1 routes completed in 0 seconds
362   o.a.camel.spring.SpringCamelContext      : Apache Camel 2.19.1 (CamelContext: camel-1) uptime 0.123 seconds
362   o.a.camel.spring.SpringCamelContext      : Apache Camel 2.19.1 (CamelContext: camel-1) is shutdown in 0.018 seconds
sunleo
  • 10,589
  • 35
  • 116
  • 196
  • Your question is a bit confusing, can you try to make it more clear and simpler what you ask. – Claus Ibsen Jan 03 '18 at 14:05
  • Are you trying to mock the output path? That's your doubt? – Ricardo Zanini Jan 03 '18 at 16:12
  • Thanks for your response pls check the updated question. – sunleo Jan 04 '18 at 05:54
  • so do you want to mock the File components, ie use something other than the file:// uri in your route? Because you can quite happily use the disk for your unit tests (no mocking required). Just use the maven target dir for input/output FilePath properties, otherwise to mock use the adviceWith method in @roman-vottner answer – stringy05 Jan 10 '18 at 02:03

4 Answers4

4

It seems like you use Camel in combination with Spring. The general setup I use here is usually the one below which I have commented to explain what the used concept are good for.

Note that I also added a generic service exposed as Spring bean, which is actually defined as Mockito mock in order to showcase how you can utilize such mocks in your test.

// These 2 annotation allow the injection of Spring beans into this test class as 
// well, ideally if you want to mock certain services defined as Spring bean 
// with i.e. Mockito
@RunWith(CamelSpringRunner.class)
@BootstrapWith(CamelTestContextBootstrapper.class)
// we are going to slightly modify the route to test in order to simplify 
// things a bit, hence we use @UseAdviceWith
@UseAdviceWith
@ContextConfiguration(loader = AnnotationConfigContextLoader.class,
    classes = { FileLineByLineRouterTest2.ContextConfig.class })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class FileLineByLineRouterTest2 {

  // Spring bean setup needed to setup the beans required while testing your route

  @Configuration
  @PropertySource({"classpath:application-test.properties"})
  public static class ContextConfig extends CamelConfiguration {

    @Override
    public List<RouteBuilder> routes() {
      final List<RouteBuilder> routes = new ArrayList<>();
      routes.add(routeToTest());
      return routes;
    }

    // This bean is required to access the property files using @Value("${...}")
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
      return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public FileLineByLineRouter routeToTest() {
      return new FileLineByLineRouter();
    }

    @Bean
    public SomeService mockService() {
      return mock(SomeService.class);
    }
  }

  @Autowired
  private CamelContext camelContext;
  @Autowired
  private ProducerTemplate template;
  @Autowired
  private SomeService someService;
  // @MockEndpoints("mock:result")
  // private MockEndpoint resultEndpoint

  @Test
  public void test() throws Exception {

    // ARRANGE

    // modify the route to test to simplify testing
    camelContext.getRouteDefinition("FileLineByLineRoute")
        .adviceWith((ModelCamelContext) camelContext, 
            new  AdviceWithRouteBuilder() {
                @Override
                public void configure() throws Exception {
                    // replace the file endpoint used in the from RouteBuilder
                    // method with a direct endpoint for easier testing
                    this.replaceFromWith("direct:start");
                    // redirect the output which should be written to a file
                    // to a mock endpoint
                    this.interceptSendToEndpoint("file:*")
                        .skipSendToOriginalEndpoint()
                        .to("mock:result");
                }
            });

    // create a mock endpoint to redirect output and define some basic assertions 
    // on either the number of processed outputs or the format of the body 
    // received by that mock endpoint. You can also use the commented out 
    // annotation approach from above instead
    // More information available at: http://camel.apache.org/mock.html
    MockEndpoint resultEndpoint = camelContext.getEndpoint("mock:result", MockEndpoint.class);
    resultEndpoint.expectedMessageCount(1);
    // other assertions on the mock endpoint possible as well here

    // define behavior of mocks
    when(someService.doSomething()).thenReturn("something");

    // ACT

    // use the producer template to send a body (and headers) to the route 
    // to test. This can litteraly be anything. A byte array, a simple string, 
    // a complex object, ...
    String testContent = "...";
    template.sendBody("direct:start", testContent);

    // ASSERT

    // check that the mock endpoint has received the actual number of bodies specified
    // before invoking the template above
    resultEndpoint.assertIsSatisfied();

    // retrieve the final body and/or headers from the processed exchanges
    // and perform your assertion against them
    List<Exchange> exchanges = resultEndpoint.getExchanges();
    assertThat(exchanges.size(), is(equalTo(1));
    Object retBody = exchanges.get(1).getOut().getBody();
    assertThat(retBody, is(equalTo(...));
  }
}

If you do want to keep the file consumer and producer in your route to test, I'd make use of JUnits TemporaryFolder rule than which could look something like this:

private MockEndpoint result;

@Rule
public TemporaryFolder sourceFolder = new TemporaryFolder();

@Before
public void init() throws Exception {
  result = context.getEndpoint("mock:result", MockEndpoint.class);

  context.getRouteDefinition("route-to-test")
    .adviceWith((ModelCamelContext) context,
                new AdviceWithRouteBuilder() {
                  @Override
                  public void configure() throws Exception {
                    replaceFromWith("file://" + sourceFolder.getRoot().toString()
                                    +"?fileExist=Move"
                                    + "&moveExisting=${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.${file:ext}"
                                    + "&tempFileName=${file:name}.tmp");
                    interceptSendToEndpoint("file:*").skipSendToOriginalEndpoint().to(result);
                  }
                });

  writeFileContent("sample1.xml", "/filesystem/outbound/sample1.xml");
  writeFileContent("sample2.xml", "/filesystem/outbound/sample2.xml");
  writeFileContent("sample3.xml", "/filesystem/outbound/sample3.xml");
  writeFileContent("sample4.xml", "/filesystem/outbound/sample4.xml");

  context.start();
}

where writeFileContent simple copies over the file contents to the temporary folder used for testing

private void writeFileContent(String name, String source) throws Exception {
  File sample = sourceFolder.newFile(name);
  byte[] bytes = IOUtils.toByteArray(getClass().getResourceAsStream(source));
  FileUtils.writeByteArrayToFile(sample, bytes);
}

The output can actually be written to a temporary test-directory as well instead of processing it through a mock endpoint. This approach is similar then defining a temporary test directory for sending files to the route. I therefore leave that approach to you.

Roman Vottner
  • 12,213
  • 5
  • 46
  • 63
  • Thank you very much for sharing your template! We used to do it this way in my old company too, so I know this is the right way to go. Could you please advise on 2 issues with the code? 1. No qualifying bean of type 'org.apache.camel.CamelContext' available 2. @Override public List routes() Should rather be @Bean? – Rick Jul 03 '18 at 02:42
  • @Rick You are of course right. I forgot to extend the config from `CamelConfiguration` which I have now added. This should add both the camel context as the routes to the Spring context and thus eligible for injection. – Roman Vottner Jul 03 '18 at 09:27
  • thank you. I'm running into a recurring problem which brought me here in the first place. However, it's still a problem. Could you please cast an eye over here https://stackoverflow.com/questions/51163123/mockito-spring-camel-autowire-fail – Rick Jul 03 '18 at 21:44
2

OK, after re-reading your comments and your updated question, I guess I understand now what you mean... your test just don't work yet.

Try this:

  • Remove extends CamelTestSupport in your Testclass. This is an alternative way to the annotation based test support.
  • Remove camelContext.start() in your Test. I probably confused you with my advice example. You only need to start the context yourself when you annotate the class with @UseAdviceWith
  • And finally, just wait a bit. For the sake of the example insert Thread.sleep(10000) in your test to give the files time to process.

Instead of a fixed sleep you could use the Camel NotifyBuilder (http://camel.apache.org/notifybuilder.html)

burki
  • 6,741
  • 1
  • 15
  • 31
  • Please check updated log, yours also not processing files.Thanks. – sunleo Jan 09 '18 at 05:46
  • You write you want to `mock in and out paths`. My answer mocks the file-endpoints **away** to simplify testing. Another answer just overwrites the file paths depending on the environment. If you want to do something else, you have to update and clarify your answer. Thanks – burki Jan 09 '18 at 16:28
  • My question is simple , I have route with process to create file by reading file.I need to mock in and out file paths for junit. Please tell me if you need anything.Also pls provide code snippet.Thanks. – sunleo Jan 10 '18 at 09:56
  • Please notice my updated answer. Your test has three main issues as described in my answer – burki Jan 10 '18 at 10:20
  • Finally in the same program Thread.sleep(2000) does the job. Pls check updated question test. Will it create any problem if I did not do step 1 and 2 you have mentioned? – sunleo Jan 10 '18 at 10:59
  • Step 1: Due to `extends CamelTestSupport` beside the annotation-based Camel test support, your test starts and stops two `CamelContext`s. You can see them in your Log-Output: `camel-1` and `camel-2`. Step 2: The statement `camelContext.start()` does probably nothing because the `CamelContext` is started automatically in a Camel test. So probably nothing changes when you delete it. – burki Jan 10 '18 at 14:10
  • Thanks for your answer! – sunleo Jan 11 '18 at 04:55
1

I think you may find your answer here : sample FilterTest.java

Here is the relevant excerpt.

@Override
protected RouteBuilder createRouteBuilder() {
    return new RouteBuilder() {
        public void configure() {
          from("direct:start").filter(header("foo").isEqualTo("bar"))
          .to("mock:result");
        }
    };
}
Tom Drake
  • 527
  • 5
  • 11
  • From the link above: @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() { from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result"); } }; } Sorry, couldn't seem to get formatting to work in a comment. – Tom Drake Jan 05 '18 at 07:57
  • Pls update your answer with some file location for above question.Thanks. – sunleo Jan 05 '18 at 08:26
1

You can extract the in and out paths to application-dev.properties (or yml).

path.in=src/main/resources/in
path.out=src/main/resources/out

Then the configure() method should change to something like that

@Override
public void configure() throws Exception {
    from("file://{{path.in}}?delete=true")
    .routeId("FileLineByLineRoute")
    .marshal().string("UTF-8")
    .split(body().tokenize("\n")).streaming()
    .process(getFileParsingProcessor())
    .to("file://{{path.out}}?fileExist=Append")
    .end();
}

Then in your tests, you can mock the properties or you can load different properties file

Chris Sekas
  • 69
  • 1
  • 4
  • I updated the question please check.File is not getting processed when I run test().File is not moved from in to out.Thanks for answer. – sunleo Jan 08 '18 at 12:12