几乎大多数软件开发人员都知道Apache Camel是一个事件驱动的框架,具有基于规则的路由和调解引擎,它由Java驱动,许多人都受益于它在与Spring集成方面提供的巨大支持。
但是如何进行单元测试呢?
我们对Camel的测试就像我们对任何java方法进行单元测试一样,通过传递输入和断言响应来进行。Camel测试是一个强大的模块,由其开发者提供,使单元测试的工作与它的使用无缝衔接。
在这篇文章中,我将带你了解3个常见的场景,这些场景几乎在每个使用Camel的应用程序中都是最广泛使用的,并使用Camel的测试模块对它们进行单元测试。完整的代码库可以在这里找到。
在此之前,请确保你的项目包含了正确的Camel测试模块依赖,如这里所见。在这篇文章中,我将使用Camel与Junit5和运行在Maven之上的Spring相结合。因此,我的选择是下面这个。
<dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-test-spring-junit5</artifactId> <version>${camel.version}</version> <scope>test</scope> </dependency>
|
场景-1
我猜这将是Camel被广泛利用的最流行的用例。使用Camel的文件组件从输入路径中挑选一个文件并进一步处理。在这个特定的案例中,我们从输入路径中挑选一个文件,并观察其格式,如果是CSV,就输出Success,如果是XLSX,就输出Fail,如果是其他文件,就不处理。同样的代码如下。
String fileInbound = UriComponentsBuilder.newInstance().scheme("file") .path(Paths.get(inboundPath).toAbsolutePath().toString()) .queryParam("include", "^.*\\.(xlsx|csv)$") .build().toString();
from(fileInbound).id("SAMPLE_FILE_ROUTE") .log("Received file: ${header.CamelFileName}") .choice() .when((exchange) -> exchange.getIn().getHeader("CamelFileName").toString() .endsWith("csv")).setHeader("status", simple("SUCCESS")).log("Hurray !! Its a CSV !!") .otherwise().setHeader("status", simple("FAILURE")).log("Oh No!! Its an XLSX..") .end();
|
单元测试将是相当直接的。模仿上述路由在测试中运行时的行为方式,然后抛出一个文件,观察上述所有场景的正确行为,即CSV的成功,XLSX的失败和txt文件的不拾取。这可能看起来很可怕,但是Camel测试模块提供了一个令人兴奋的方法,在测试时像正常运行一样启动路由,并允许在文件拾取时验证正/负场景,允许将最终响应路由到一个模拟端点。
实现如下:
//Extend the class with CamelTestSupport which Camel Test provides class SampleFileRouteTest extends CamelTestSupport { //Override the createRouteBuilder() and return an object of the actual route class under test @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new SampleFileRoute("src/test/resources"); } //Set-Up the mock route to listenForCompletion response once the route is triggered @Override protected void doPostSetup() throws Exception { AdviceWith.adviceWith(context, "SAMPLE_FILE_ROUTE", a -> a.onCompletion().onCompleteOnly().to("mock:listenForComplete")); } //And Finally the test cases for all 3 scenarios, namely Success/Fail and Invalid File @Test void testForSuccessValidFile() throws IOException, InterruptedException { inboundFile = new File(inboundPath.toString(), "test.csv"); inboundFile.createNewFile(); listenForComplete.expectedHeaderReceived("status", "SUCCESS"); listenForComplete.assertIsSatisfied(); }
@Test void testForFailValidFile() throws IOException, InterruptedException { inboundFile = new File(inboundPath.toString(), "test.xlsx"); inboundFile.createNewFile(); listenForComplete.expectedHeaderReceived("status", "FAILURE"); listenForComplete.assertIsSatisfied(); }
@Test void testForInvalidFile() throws IOException, InterruptedException { inboundFile = new File(inboundPath.toString(), "test.txt"); inboundFile.createNewFile(); listenForComplete.expectedHeaderReceived("status","SUCCESS"); listenForComplete.assertIsNotSatisfied(); } }
|
场景-2
另一个有趣的用例是FTP,我广泛地看到人们在使用Camel。Camel提供了一个叫FTP的组件,它以简单和非常干净的方式为所有的FTP传输提供了开箱即用的支持。我们在这方面的用例将是相当直接的。从入站路径中选取一个文件并将其SFTP到远程服务器的一个路径。成功时打印出一个日志,表明传输是通过的,失败时打印同样的日志。下面是这个逻辑的代码。
//Prepare the respective inbound and outbound URLS String fileInbound = UriComponentsBuilder.newInstance().scheme("file") .path(inboundPath) .build().toString();
String outboundLocation = UriComponentsBuilder.newInstance() .scheme("sftp").host(sftpInfo.getHost()).port(sftpInfo.getPort()).userInfo(sftpInfo.getUser()) .path(sftpInfo.getPath()).queryParam("preferredAuthentications", "password") .queryParam("password", sftpInfo.getPassword()) .queryParam("jschLoggingLevel", "WARN") .build().toString();
//Use Camel-FTP Support and effect the transfer.. namely pick from the inbound and send to output //and observe success/failures from(fileInbound).id("SAMPLE_SFTP_ROUTE") .log("Starting SFTP Transfer for File: ${header.CamelFileName}") .to(outboundLocation) .log("SFTP Transfer Success for File: ${header.CamelFileName}");
onException(Exception.class) .maximumRedeliveries(2) .redeliveryDelay(20) .log("SFTP Transfer Failure for File: ${header.CamelFileName}") .handled(true);
|
测试案例也很简单,就是观察连接到SFTP服务器的正常启动路线,以及正向流量的正常传输,而对于负向流量,路线无法启动,网络断开,意味着传输没有发生。此刻你可能会想到我们将在这里使用的SFTP服务器的下落。对于那些听说过fake-sftp-server-lambda这个内存SFTP解决方案的人来说,这并不奇怪。对于那些第一次听说的人来说,fake-sftp-server是一个MIT许可的项目,它写在Apache SSHD项目的SFTP服务器之上,以方便在测试执行中使用内存SFTP服务器。利用Camel和Fake-Sftp-Server的力量,对同样的测试进行了如下操作。
//Extend the test class with Test Support Camel provides class SampleSftpRouteTest extends CamelTestSupport { //Start the in-memory SFTP Server using Fake-Sftp-Server-Lambda and make it listen @Override @BeforeEach public void setUp() throws Exception { withSftpServer(server -> { server.addUser("tst", "tst"); sftpInfo = SftpInfo.builder().host("0.0.0.0").port(server.getPort()).path("/").build(); }); super.setUp(); } //Test Both the Success and Fail Scenarios. //For Success - Test if the file is transfered successful upon created in I/P Path //For Failure - Test if the route throws exception upon start and the file transfer does'nt happen @Test void testsftpSuccess() throws Exception { inboundFile = new File(inboundPath.toString(), "test.txt"); inboundFile.createNewFile(); await().atMost(10, TimeUnit.SECONDS); withSftpServer(server -> assertTrue(server.existsFile("/test.txt"))); assertFalse(inboundFile.exists()); }
@Test void testsftpFailure() throws Exception { sftpInfo.setUser("user"); sftpInfo.setPassword("wrong"); inboundFile = new File(inboundPath.toString(), "test.txt"); inboundFile.createNewFile(); await().atMost(10, TimeUnit.SECONDS); withSftpServer(server -> assertFalse(server.existsFile("/test.txt"))); assertTrue(inboundFile.exists()); }
}
|
场景-3
这无疑是最有趣的方面,对于大多数开发者来说,使用Camel是为了它提供的多跳支持。当生产者发送消息交换时,Camel Direct提供了对任何消费者的直接、同步的调用。这个端点可用于连接同一Camel上下文中的现有路由。在我们的用例中,我们实现了所提供的设施,并以同步的方式在各个逻辑块中进行路由。这些代码或多或少都是不言自明的。不过,如果有不清楚的地方,请随时查看评论。
//Build the route as below. It all starts from India and proceeds to Delhi, Mumbai, Kolkata and Chennai //In each and every stop, it goes into individual logics and has it executed. //The block of individual logic for Delhi is also given below. from("direct:India").id("SAMPLE_MULTIHOP_ROUTE") .log("Welcome to India !!") .to("direct:Delhi") .to("direct:Mumbai") .to("direct:Kolkata") .to("direct:Chennai");
from("direct:Delhi") .bean(Delhi.class);
from("direct:Mumbai") .bean(Mumbai.class);
from("direct:Kolkata") .bean(Kolkata.class);
from("direct:Chennai") .bean(Chennai.class);
//Execution Logic inside Delhi.class @Component public class Delhi {
@Handler public void handler(@RequestBody String name) { System.out.println("Hello Mr " + name + "!!! Welcome to Delhi !!"); } }
|
测试这一点非常简单,使用Camel的Producer-Template明确地触发起始点,然后模拟所有其他停止点的豆子,并验证与模拟对象的交互。注意,重要的是在Camel测试上下文中注入被模拟的对象,以观察和断言其行为。同样的测试将如下所示。
@ExtendWith(MockitoExtension.class) class SampleMultiHopRouteTest extends CamelTestSupport {
@InjectMocks private SampleMultiHopRoute sampleMultiHopRoute;
@Mock private Delhi delhi;
@Mock private Mumbai mumbai;
@Mock private Kolkata kolkata;
@Mock private Chennai chennai;
@Override protected RoutesBuilder createRouteBuilder() throws Exception { Registry registry = context.getRegistry(); registry.bind("delhi", Delhi.class, delhi); registry.bind("mumbai", Mumbai.class, mumbai); registry.bind("kolkata", Kolkata.class, kolkata); registry.bind("chennai", Chennai.class, chennai); return sampleMultiHopRoute; }
@Test void testMultiHopRoute() { doNothing().when(delhi).handler("Junit"); doNothing().when(mumbai).handler("Junit"); doNothing().when(kolkata).handler("Junit"); doNothing().when(chennai).handler("Junit"); template.sendBody("direct:India", "Junit"); verify(delhi).handler("Junit"); verify(mumbai).handler("Junit"); verify(kolkata).handler("Junit"); verify(chennai).handler("Junit"); } }
|
还有许多其他惊人的用例,Camel实际上被用于这些用例。当然,所有这些都有非常令人兴奋的方法来进行单元测试,Camel-Test在每一个单元测试中都发挥了巨大的作用,从提供一个正确的环境来实时进行单元测试到断言这些测试。是的!有一些调整是必须做的,以模拟用例的实际工作方式,使其得到全面的测试。但无论如何,Camel-测试支持是其开发团队提供的一个奇妙的功能,使其测试强大而简单。