camel系列-测试

介绍 Camel 如何使用单元测试

camel 测试方式

CamelTestSupport

  1. 引入 maven 依赖
1
2
3
4
5
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test</artifactId>
<scope>test</scope>
</dependency>
  1. 编写一个 RouteBuilder
1
2
3
4
5
6
7
8
9
10
11
/**
* Route builder that prepends the body of the exchange that is passed to it.
*/
public class SimpleTransformRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("direct:in")
.transform(simple("Modified: ${body}"))
.to("mock:out");
}
}
  1. 编写单元测试
  • 创建一个继承自 org.apache.camel.test.junit4.CamelTestSupport 的类
  • 重写 createRouteBuilder 方法,返回自定义的 RouteBuilder
  • 编写单元测试方法
    • 测试消息通过 CamelTestSupport 的 template(ProducerTemplate 实例)进行发送
    • MockEndpoint 用于对断言结果进行验证
    • assertMockEndpointsSatisfied 方法用于触发验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SimpleTransformRouteTest extends CamelTestSupport {

@Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new SimpleTransformRoute();
}

@Test
public void testPayloadIsTransformed() throws InterruptedException {
MockEndpoint mockOut = getMockEndpoint("mock:out");
mockOut.setExpectedMessageCount(1);
mockOut.message(0).body().isEqualTo("Modified: Cheese");

template.sendBody("direct:in", "Cheese");

assertMockEndpointsSatisfied();
}
}

使用 DI 进行测试

  1. 通过注解的方法指定路由生产端点和到达的端点
  2. 调用方法可以去掉缺省的端点 url
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SimpleTransformDIRouteTest extends CamelTestSupport {

@Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;

@EndpointInject(uri = "mock:out")
private MockEndpoint mockOut;

@Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new SimpleTransformRoute();
}

@Test
public void testPayloadIsTransformed() throws InterruptedException {
mockOut.setExpectedMessageCount(1);
mockOut.message(0).body().isEqualTo("Modified: Cheese");

template.sendBody("Cheese");

assertMockEndpointsSatisfied();
}
}

CamelContext

使用 CamelContext 初始化创建路由进行测试,这是使用 Camel 最原始的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class FirstPrinciplesRouteBuilderTest {
private CamelContext camelContext;

@Before
public void setUpContext() throws Exception {
this.camelContext = new DefaultCamelContext();
camelContext.addRoutes(new SimpleTransformRoute());
camelContext.start();
}

@After
public void cleanUpContext() throws Exception {
camelContext.stop();
}

@Test
public void testPayloadIsTransformed() throws InterruptedException {
MockEndpoint out = camelContext.getEndpoint("mock:out", MockEndpoint.class);

out.setExpectedMessageCount(1);
out.message(0).body().isEqualTo("Modified: Cheese");

ProducerTemplate producerTemplate = camelContext.createProducerTemplate();
producerTemplate.sendBody("direct:in", "Cheese");

out.assertIsSatisfied();
}
}

自定义测试上下文配置

下面测试代码做了以下自定义配置

  1. 添加了 seta 组件
  2. 自定义了 RouteBuilder,而不是采用单独的类引用方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class CustomCamelContextConfigTest extends CamelTestSupport {

@Override
public CamelContext createCamelContext() {
CamelContext context = new DefaultCamelContext();
// plug in a seda component, as we don't really need an embedded broker
context.addComponent("activemq", new SedaComponent());
return context;
}

@Override
public RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
@Override
public void configure() {
from("direct:in")
.to("activemq:orders");

from("activemq:orders")
.to("mock:out");
}
};
}

@Test
public void testMessagesFlowOverQueue() throws InterruptedException {
MockEndpoint out = getMockEndpoint("mock:out");
out.setExpectedMessageCount(1);
out.expectedBodiesReceived("hello");

template.sendBody("direct:in", "hello");

assertMockEndpointsSatisfied();
}
}

使用 MockEndpoint 验证路由逻辑

Camel 框架中内置了使用 MockEndpoint 验证消息流的能力。 camel-core 库中的 Mock 组件为您提供了一个测试 DSL,允许您验证哪些消息已到达各种命名模拟:路由中定义的端点。 下面将描述如何使用这个模拟 DSL。

定义路由

在路由中使用 mock 作为 endpoint

1
2
3
4
5
6
7
8
9
public class MockReplyRoute extends RouteBuilder {

@Override
public void configure() {
from("direct:in")
.inOut("mock:replying")
.to("mock:out");
}
}

获取 MockEndpoint

  1. 如果集成自 CamelTestSupport,使用 getMockEndpoint 方法获取
  2. 使用 camelContext 的 getEndpoint 方法获取
1
2
3
4
5
MockEndpoint mockCamel = getMockEndpoint("mock:camel");

MockEndpoint mockCamel =
camelContext.getEndpoint(
"mock:camel", MockEndpoint.class);

定义验证流程

使用 MockEndpoint 对结果进行验证

1
2
3
4
5
6
@Test
public void testWhen() throws Exception {
MockEndpoint mockCamel = getMockEndpoint("mock:camel");
mockCamel.expectedMessageCount(2);
mockCamel.message(0).body().isEqualTo("Camel Rocks");
}

发送消息

通过ProducerTemplate 发送消息

验证结果

调用 assertIsSatisfied 方法进行发起结果验证流程

1
mockCamel.assertIsSatisfied();

在 Spring 中验证路由逻辑

  1. 继承自 CamelSpringTestSupport
  2. 重写 createApplicationContext 方法创建 ApplicationContext

版本 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SimpleTransformSpringTest extends CamelSpringTestSupport {

@Override
protected AbstractApplicationContext createApplicationContext() {
return new ClassPathXmlApplicationContext("/spring/test-properties-context.xml",
"/META-INF/spring/simpleTransform-context.xml");
}

@Test
public void testPayloadIsTransformed() throws InterruptedException {
MockEndpoint mockOut = getMockEndpoint("mock:out");
mockOut.setExpectedMessageCount(1);
mockOut.message(0).body().isEqualTo("Modified: Cheese");

template.sendBody("direct:in", "Cheese");

assertMockEndpointsSatisfied();
}
}

使用 DI 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SimpleTransformAnnotationsSpringTest extends CamelSpringTestSupport {

@Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;

@EndpointInject(uri = "mock:out")
private MockEndpoint mockOut;

@Override
protected AbstractApplicationContext createApplicationContext() {
return new ClassPathXmlApplicationContext("/spring/test-properties-context.xml",
"/META-INF/spring/simpleTransform-context.xml");
}

@Test
public void testPayloadIsTransformed() throws InterruptedException {
mockOut.setExpectedMessageCount(1);
mockOut.message(0).body().isEqualTo("Modified: Cheese");

producerTemplate.sendBody("Cheese");

assertMockEndpointsSatisfied();
}
}

配置属性注入

  1. 重写 useOverridePropertiesWithPropertiesComponent 方法,注入配置属性
1
2
3
4
5
6
7
@Override
protected Properties useOverridePropertiesWithPropertiesComponent() {
Properties properties = new Properties();
properties.setProperty("start.endpoint", "direct:in");
properties.setProperty("end.endpoint", "mock:out");
return properties;
}
  1. 在路由配置文件中使用配置属性
1
2
3
4
5
6
7
8
9
10
11
<camelContext xmlns="http://camel.apache.org/schema/spring">
<propertyPlaceholder id="properties" location="ref:ctx.properties"/>
<route>
<from uri="{{start.endpoint}}"/>
<transform>
<simple>Modified: ${body}</simple>
</transform>
<log message="Set message to ${body}"/>
<to uri="{{end.endpoint}}"/>
</route>
</camelContext>

使用 Spring 注解进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RunWith(CamelSpringJUnit4ClassRunner.class)
@ContextConfiguration({"/spring/test-properties-context.xml",
"/META-INF/spring/simpleTransform-context.xml"})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class SimpleTransformEnhancedSpringTest {

@Autowired
private CamelContext camelContext;

@Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;

@EndpointInject(uri = "mock:out")
private MockEndpoint mockOut;

@Test
public void testPayloadIsTransformed() throws InterruptedException {
mockOut.setExpectedMessageCount(1);
mockOut.message(0).body().isEqualTo("Modified: Cheese");

producerTemplate.sendBody("Cheese");

MockEndpoint.assertIsSatisfied(camelContext);
}
}