建议
AdviceWith 用于测试 Camel 路由,您可以在测试之前为现有路由提供建议。
adviceWith
允许在测试运行前更改路线上的某些因素,例如
-
拦截发送到端点 -
用另一个端点替换传入端点 -
删除或替换路由节点 -
在路由中插入新节点 -
模拟端点
AdviceWithRouteBuilder
提供所有这些功能。是一个专门的 RouteBuilder
。.
建议使用路由生成器 api
AdviceWithRouteBuilder
对常规 RouteBuilder
进行了扩展,增加了用于建议路线的专门方法。下表列出了最常用的方法:
方法 | 说明 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
模式匹配
模式选项用于匹配。它使用与截取相同的规则,按以下顺序应用:
-
匹配精确
-
通过通配符匹配 (*
) -
正则表达式匹配
例如,要精确匹配,可以使用 weaveById("foo")
,这样就只能匹配路由中值为 foo 的 id。通配符是指以 *
字符结尾的模式,例如: weaveById("foo*")
将匹配任何以 foo 开头的 id,如:foo、foobar、foobie 等。正则表达式更高级,可以匹配多个 id,如 weaveById("(foo|bar)")
将同时匹配 foo 和 bar。
如果要在精确的端点 URI 上匹配模式,请注意 URI 选项的排序可能会受到影响,因此最好使用通配符进行匹配。
例如,使用 mockEndpointsAndSkip("activemq:queue:foo?*")
来匹配 foo 队列,并忽略任何选项。
使用建议
要提供建议,您需要使用 AdviceWithRouteBuilder
来操作路由。但首先您需要选择要操作的路由,这可以通过路由 ID 或路由索引来实现。
AdviceWith.adviceWith(context, "myRoute", new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
weaveAddLast().to("mock:result");
}
});
我们采用 Java lambda 风格,为路由建议引入了更现代化的应用程序接口。
下面我们将为 ID 为 myRoute 的路线提供建议:
AdviceWith.adviceWith(context, "myRoute", a ->
a.weaveAddLast().to("mock:result")
);
变量 a
是 AdviceWithRouteBuilder
的 lambda 样式,可作为内联路由操作的快捷方式。
在使用 adviceWith 之前,最好先告知骆驼正在使用建议,这将在下一节中介绍。 |
在测试期间提供建议
使用 adviceWith
时,Camel 会重新启动建议路由。出现这种情况的原因是路由被操作,Camel 需要:
-
停止现有路线 -
删除现有路线 -
添加新的建议路线 -
开始新路线
在单元测试启动过程中,每个建议路由都会发生这种情况。这种情况发生得很快:路由启动后会立即停止并移除。这是我们不希望看到的行为;我们希望避免重新启动建议路由。可以通过以下步骤解决这个问题:
-
告诉 Camel 正在建议路线,这将阻止 Camel 自动启动路线。 -
建议在单元测试方法中使用路由 -
建议路线后启动 Camel
在使用 camel-test-junit5 进行单元测试时,您可以通过覆盖 CamelTestSupport
中的 isUsedAdviceWith
方法来告诉 Camel 正在使用建议,如图所示:
public class MyAdviceWithTest extends CamelTestSupport {
@Override
public boolean isUseAdviceWith() {
return true; // turn on advice with
}
}
或者,在使用 camel-test-spring-junit5 进行单元测试时,可以使用如图所示的 @UseAdviceWith
注解:
@UseAdviceWith
public class MyAdviceWithTest extends CamelSpringTestSupport {
}
然后,你建议启动骆驼所遵循的路线:
@Test
public void testMockEndpoints() throws Exception {
AdviceWith.adviceWith(context, "myRoute", a ->
a.mockEndpoints();
);
context.start();
在上面的单元测试方法中,我们首先根据 ID 建议路由,并在其中自动模拟所有端点。然后启动 Camel。
建议路线前后的记录
使用 adviceWith
时,Camel 会以 XML 格式自动记录每条建议路线的前后信息。
不过,这需要将 camel-xml-jaxb
作为依赖项,如果使用 Maven,可以将其添加为测试作用域:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-xml-jaxb</artifactId>
<version>x.x.x</version>
<!-- use the same version as your Camel core version -->
<scope>test</scope>
</dependency>
如图所示,通过将日志记录设置为 false
,可以关闭 XML 日志记录:
AdviceWith.adviceWith(context, "myRoute", false, a ->
a.mockEndpoints();
);
替换路由端点
您可能已经建立了从数据库、消息代理、云系统或其他外部系统消费的端点开始的 Camel 路由。
为了便于对这类路由进行单元测试,可以用 direct、seda、stub 等内部端点替换路由输入端点。
下面说明了如何做到这一点:
@Test
public void testReplaceFrom() throws Exception {
AdviceWith.adviceWith(context, "myRoute", a ->
a.replaceFromWith("direct:start");
);
context.start();
这样,ID 为 myRoute 的路由中的输入端点(from)就被直接端点取代了,从而便于在单元测试时向路由发送消息。
模拟端点
使用 mockEndpoints
方法提示路由时,Camel 会在启动过程中记录已提示的端点及其相应的模拟 uri,例如
INFO ceptSendToMockEndpointStrategy - Adviced endpoint [seda://camel] with mock endpoint [mock:seda:camel]
INFO ceptSendToMockEndpointStrategy - Adviced endpoint [seda://other] with mock endpoint [mock:seda:other]
在这里,Camel 建议使用两个端点:
-
seda:camel -→ mock:seda:camel
-
seda:other -→ mock:seda:other
这样就可以在单元测试中使用模拟端点进行测试,例如
public void testMockEndpoints() throws Exception {
// advice the route goes here
// start camel after advice
// use the auto mocked uris during testing
getMockEndpoint("mock:seda:camel").expectedMessageCount(3);
getMockEndpoint("mock:seda:other").expectedMessageCount(1);
// send messages
MockEndpoint.assertIsSatisfied(context);
}
在 Camel 路由中使用 adviceWith
替换输入端点或模拟端点只是路由操作功能的开始。下一节将介绍更深入的操作方法。
利用编织修改路线
测试 Camel 路由时,可以使用 adviceWith
在测试前编织路由。
以下方法适用于 weave
方法:
方法 | 说明 |
---|---|
|
|
|
|
|
|
|
|
例如,给定以下路线:
from("direct:start")
.to("mock:foo")
.to("mock:bar").id("bar")
.to("mock:result");
然后,让我们来看看如何在单元测试中使用这四种方法
替换
AdviceWith.adviceWith(context.getRouteDefinitions().get(0), context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
// weave the node in the route which has id = bar
// and replace it with the following route path
weaveById("bar").replace().multicast().to("mock:a").to("mock:b");
}
});
在本例中,我们用 .multicast().to("mock:a").to("mock:b")
代替 .to("mock:bar").id("bar")
。.这就意味着,我们不是向 mock:bar 端点发送信息,而是向 mock:a 和 mock:b 端点进行组播。
删除
在下面的示例中,我们只需将路由中的 .to("mock:bar").id("bar")
删除即可:
AdviceWith.adviceWith(context.getRouteDefinitions().get(0), context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
// weave the node in the route which has id = bar and remove it
weaveById("bar").remove();
}
});
前
在下面的示例中,我们在id 为 "bar "的节点之前添加了以下节点 to("mock:a").transform(constant("Bye World"))
。
AdviceWith.adviceWith(context.getRouteDefinitions().get(0), context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
// weave the node in the route which has id = bar
// and insert the following route path before the adviced node
weaveById("bar").before().to("mock:a").transform(constant("Bye World"));
}
});
这意味着之前发送到 mock:bar 的信息将被转换为恒定信息 "再见世界"。
后
在下面的示例中,我们在id 为 "bar "的节点后添加了以下节点 to("mock:a").transform(constant("Bye World"))
。
AdviceWith.adviceWith(context.getRouteDefinitions().get(0), context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
// weave the node in the route which has id = bar
// and insert the following route path after the advice node
weaveById("bar").after().to("mock:a").transform(constant("Bye World"));
}
});
这意味着,在 mock:bar 之后发送的信息将转变为恒定信息 "再见世界"。
不使用 ID 进行编织
编织路由时,需要使用 weaveBy
方法中的一种作为标准,在路由图中选择一个或多个节点。
假设您在路由中使用了拆分 EIP,那么您可以使用 weaveByType
来选择该 EIP。给定以下路由:
from("file:inbox").routeId("inbox")
.split(body())
.transform(simple("${body.toLowerCase()}"))
.to("mock:line")
.end()
.to("mock:combined");
由于路由中只有一个分路器 EIP,您可以使用 weaveByType
查找路由中的这一个分路器。使用 weaveByType
时,需要输入 EIP 的模型类型。模型类型的名称使用 _name_Definition 模式。
weaveByType(SplitDefinition.class)
.before()
.transform(simple("${body},Camel is awesome"));
在这里,我们编织并选择拆分 EIP,然后编织消息转换,在调用拆分器之前对其进行处理。这就意味着,信息正文被附加了 "Camel is awesome"(Camel真棒)。
旅游编织
weaveByToUri
是一种方便的方法,可轻松编织一条向给定端点 URI 或模式发送消息的 Camel 路由。
下面的路由在基于内容的路由器 EIP 中有两个分支:
from("direct:start")
.choice()
.when(header("foo")).to("direct:branch-1")
.otherwise()
.to("direct:branch-2");
然后,我们想轻松地对该路由进行单元测试,以确定消息是在分支-1 还是分支-2 发送的。如图所示,我们可以使用 weaveByToUri
进行测试:
weaveByToUri("direct:branch*").replace().to("mock:cheese");
请注意, weaveByToUri
方法使用通配符 ( *
) 来匹配两个分支。
您也可以使用 mockEndpoints 自动模拟,而不是上例中的 weaveByToUri 。 weave 方法可以对路由进行更多操作,如消息转换、消息路由等。 |
先编织后编织
weaveAddFirst
和 weaveAddLast
是一种速记方法,可以方便地向路由添加节点。这些方法只能向现有路由添加节点。如果要对路由进行操作,请使用前面介绍过的其他 weave
方法。
weaveAddFirst
方法在路径开头添加, weaveAddLast
方法在路径结尾添加。使用它们的效果与其他 weaveBy
方法相同,请参见上文的示例。
利用节点选择进行编织
weaveBy
方法会选择所有匹配的节点,这些节点可以是无节点、一个节点、两个节点或多个节点。在这种情况下,您可能希望将选择范围缩小到某个特定节点。这可以通过使用 select 方法来实现:
-
selectFirst
仅选择第一个节点。 -
selectLast
仅选择最后一个节点。 -
selectIndex(index)
仅选择第 n 个节点。索引为零。 -
selectRange(from, to)
选择给定范围内的节点。索引为零。 -
maxDeep(level)
将选择范围限制在 Camel 路由树最深的 N 层。第一层是编号 1。因此第 2 个节点是第一层节点的子节点。
下面的路由有多个过滤器 EIP,我们只想建议第二个过滤器。
from("file:inbox").routeId("inbox")
.filter(header("foo"))
.to("mock:foo")
.end()
.to("mock:a")
.filter(header("bar"))
.to("mock:bar")
.end()
.to("mock:b")
.filter(header("cheese"))
.to("mock:cheese")
.end()
.to("mock:c")
然后,您可以使用 weaveByType
来匹配过滤 EIP,使用 selectIndex 来匹配找到的第 2 个 EIP:
weaveByType(FilterDefinition.class).selectIndex(1).replace().to("mock:changed");