主题及目标观众
本文在这里展示了如何在Spring App中与Apache Camel进行集成测试,因为在文档中没有对其进行精确描述。 熟悉Apache Camel功能的初学者或开发人员都可以阅读。 而且,具有一些知识则会更容易理解它。
介绍
使用此方法,您将能够从头到尾启动一个现有路由(无论是否使用真实数据库),拦截路由各部分之间的交换,并检查标题或正文是否包含正确的值。
我一直在具有XML配置和用于数据库模拟的DBUnit的经典Spring项目中进行此操作。 希望这会给您一些线索。
安装
如果您的项目尚未完成,则应添加骆驼的测试依赖项。 请参阅以下Maven用户:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test</artifactId>
<version>${camel.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-spring</artifactId>
<version>${camel.version}</version>
<scope>test</scope>
</dependency>
Camel 路线的例子
以下路线的目标很简单:
- 首先,它检查数据库中是否存在和ImportDocumentProcess对象,并将其添加为交换头
- 然后,在数据库中添加一个ImportDocumentTraitement(链接到先前的ImportDocumentProcess)。
这是这条路线的代码:
@Component
public class TestExampleRoute extends SpringRouteBuilder {
public static final String ENDPOINT_EXAMPLE = "direct:testExampleEndpoint";
@Override
public void configure() throws Exception {
from(ENDPOINT_EXAMPLE).routeId("testExample")
.bean(TestExampleProcessor.class, "getImportDocumentProcess").id("getImportDocumentProcess")
.bean(TestExampleProcessor.class, "createImportDocumentTraitement").id("createImportDocumentTraitement")
.to("com.pack.camel.routeshowAll=true&multiline=true");
}
}
路线上的ID不是强制性的,你可以使用bean字符串之后。然而,我认为使用id可以被认为是一个很好的实践,以防将来您的路由字符串发生变化。
Camel 处理器的例子
处理器只包含路由所需的方法。 它只是一个经典的Java Bean,其中包含几种方法。 您还可以实现Processor并覆盖处理方法。
参见下面的代码:
@Component("testExampleProcessor")
public class TestExampleProcessor {
private static final Logger LOGGER = LogManager.getLogger(TestExampleProcessor.class);
@Autowired
public ImportDocumentTraitementServiceImpl importDocumentTraitementService;
@Autowired
public ImportDocumentProcessDAOImpl importDocumentProcessDAO;
@Autowired
public ImportDocumentTraitementDAOImpl importDocumentTraitementDAO;
// ---- Constants to name camel headers and bodies
public static final String HEADER_ENTREPRISE = "entreprise";
public static final String HEADER_UTILISATEUR = "utilisateur";
public static final String HEADER_IMPORTDOCPROCESS = "importDocumentProcess";
public void getImportDocumentProcess(@Header(HEADER_ENTREPRISE) Entreprise entreprise, Exchange exchange) {
LOGGER.info("Entering TestExampleProcessor method : getImportDocumentProcess");
Utilisateur utilisateur = SessionUtils.getUtilisateur();
ImportDocumentProcess importDocumentProcess = importDocumentProcessDAO.getImportDocumentProcessByEntreprise(
entreprise);
exchange.getIn().setHeader(HEADER_UTILISATEUR, utilisateur);
exchange.getIn().setHeader(HEADER_IMPORTDOCPROCESS, importDocumentProcess);
}
public void createImportDocumentTraitement(@Header(HEADER_ENTREPRISE) Entreprise entreprise,
@Header(HEADER_UTILISATEUR) Utilisateur utilisateur,
@Header(HEADER_IMPORTDOCPROCESS) ImportDocumentProcess importDocumentProcess, Exchange exchange) {
LOGGER.info("Entering TestExampleProcessor method : createImportDocumentTraitement");
long nbImportTraitementBefore = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();
ImportDocumentTraitement importDocumentTraitement = this.importDocumentTraitementService.createImportDocumentTraitement(
entreprise, utilisateur, importDocumentProcess, "md5_fichier_example_test", "fichier_example_test.xml");
long nbImportTraitementAfter = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();
exchange.getIn().setHeader("nbImportTraitementBefore", Long.valueOf(nbImportTraitementBefore));
exchange.getIn().setHeader("nbImportTraitementAfter", Long.valueOf(nbImportTraitementAfter));
exchange.getIn().setHeader("importDocumentTraitement", importDocumentTraitement);
}
// Rest of the code contains getters and setters for imported dependencies
}
在这里无需多说,只是我们使用交换将对象从一个部分转移到另一部分。 这是通常在我的项目中完成的方式,因为我们要处理的过程非常复杂。
Camel 测试类
测试类将在示例路由上触发并运行测试。 我们还使用DBUNit模拟具有一些预定义值的数据库。
首先,我们使用抽象类来在每个Camel Integration测试之间共享通用注释。 参见下面的代码:
@RunWith(CamelSpringRunner.class)
@BootstrapWith(CamelTestContextBootstrapper.class)
@ContextConfiguration(locations = { "classpath:/test-beans.xml" })
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
DbUnitTestExecutionListener.class })
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) // Not always relevant, See explanation below
public abstract class AbstractCamelTI {
}
重要说明:此处@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
的注释用于重新加载每次测试的骆驼上下文。这样,每种测试方法都具有新的重新初始化上下文。但是您也需要为每个测试重新定义模拟的端点。如果您需要重构大型骆驼处理器并独立测试每个零件,则可以提供帮助。否则,只需删除此注释,因为它有一些缺点:
- 这些测试不再是真正的集成测试,您必须为每个测试删除并编织您的路径部分,这可能有些繁重
- 为每个测试重新加载上下文需要一段时间,并且可能会大大增加测试的总体运行时间。从长远来看,这可能会让人沮丧
测试课程配置
@DatabaseSetup(value = { "/db_data/dao/common.xml", "/db_data/dao/dataForImportDocumentTests.xml" })
public class TestExampleProcessorTest extends AbstractCamelTI {
@Autowired
protected CamelContext camelContext;
@EndpointInject(uri = "mock:catchTestEndpoint")
protected MockEndpoint mockEndpoint;
@Produce(uri = TestExampleRoute.ENDPOINT_EXAMPLE)
protected ProducerTemplate template;
@Autowired
ImportDocumentTraitementDAO importDocumentTraitementDAO;
// -- Variables for tests
ImportDocumentProcess importDocumentProcess;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
importDocumentProcess = new ImportDocumentProcess();
//specific implementation of your choice
}
}
第一个测试
此测试会触发路线的第一部分并将其引向模拟端点这样我们就可以测试ImportDocumentProcess已正确选择并放入标题中:
@Test
public void processCorrectlyObtained_getImportDocumentProcess() throws Exception {
camelContext.getRouteDefinitions().get(0).adviceWith(camelContext, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
weaveById("getImportDocumentProcess").after().to(mockEndpoint);
}
});
// -- Launching the route
camelContext.start();
template.sendBodyAndHeader(null, "entreprise", company);
mockEndpoint.expectedMessageCount(1);
mockEndpoint.expectedHeaderReceived(TestExampleProcessor.HEADER_UTILISATEUR, null);
mockEndpoint.expectedHeaderReceived(TestExampleProcessor.HEADER_IMPORTDOCPROCESS, importDocumentProcess);
mockEndpoint.assertIsSatisfied();
camelContext.stop();
}
第二个测试
该测试只是简化了整个路线:
@Test
public void traitementCorrectlyCreated_createImportDocumentTraitement() throws Exception {
camelContext.getRouteDefinitions().get(0).adviceWith(camelContext, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
weaveById("createImportDocumentTraitement").after().to(mockEndpoint);
}
});
// -- Launching the route
camelContext.start();
Exchange exchange = new DefaultExchange(camelContext);
exchange.getIn().setHeader(TestExampleProcessor.HEADER_ENTREPRISE, company);
exchange.getIn().setHeader(TestExampleProcessor.HEADER_UTILISATEUR, null); // No user in this case
exchange.getIn().setHeader(TestExampleProcessor.HEADER_IMPORTDOCPROCESS, importDocumentProcess);
long numberOfTraitementBefore = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();
template.send(exchange);
mockEndpoint.expectedMessageCount(1);
mockEndpoint.assertIsSatisfied();
camelContext.stop();
long numberOfTraitementAfter = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();
assertEquals(numberOfTraitementBefore + 1L, numberOfTraitementAfter);
}
良好做法
如果要避免在简单错误上浪费时间,可以遵循一些准则。
路线编号
在此示例中,我使用以下行获取路由并编织测试端点:
camelContext.getRouteDefinitions().get(0).adviceWith(camelContext, new AdviceWithRouteBuilder() { [...] });
然而更好的方法是影响ID到您的路线,并使用以下行来获取和编织它们:
camelContext.getRouteDefinition("routeId").adviceWith(camelContext, new AdviceWithRouteBuilder() { [...] });
这个方法更简洁一些,因为您不必手动确认要测试的路由。重构路由也比较简单,因为您可以在不更改路由的id的情况下更改路由的字符串。