001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.testng;
018
019 import java.io.InputStream;
020 import java.util.Hashtable;
021 import java.util.Map;
022 import java.util.Properties;
023 import java.util.concurrent.TimeUnit;
024 import java.util.concurrent.atomic.AtomicBoolean;
025 import javax.naming.Context;
026 import javax.naming.InitialContext;
027
028 import org.apache.camel.CamelContext;
029 import org.apache.camel.ConsumerTemplate;
030 import org.apache.camel.Endpoint;
031 import org.apache.camel.Exchange;
032 import org.apache.camel.Expression;
033 import org.apache.camel.Message;
034 import org.apache.camel.Predicate;
035 import org.apache.camel.Processor;
036 import org.apache.camel.ProducerTemplate;
037 import org.apache.camel.Service;
038 import org.apache.camel.builder.RouteBuilder;
039 import org.apache.camel.component.mock.MockEndpoint;
040 import org.apache.camel.impl.BreakpointSupport;
041 import org.apache.camel.impl.DefaultCamelContext;
042 import org.apache.camel.impl.DefaultDebugger;
043 import org.apache.camel.impl.InterceptSendToMockEndpointStrategy;
044 import org.apache.camel.impl.JndiRegistry;
045 import org.apache.camel.management.JmxSystemPropertyKeys;
046 import org.apache.camel.model.ProcessorDefinition;
047 import org.apache.camel.spi.Language;
048 import org.apache.camel.spring.CamelBeanPostProcessor;
049 import org.apache.camel.util.StopWatch;
050 import org.apache.camel.util.TimeUtils;
051 import org.junit.AfterClass;
052 import org.slf4j.Logger;
053 import org.slf4j.LoggerFactory;
054 import org.testng.annotations.AfterMethod;
055 import org.testng.annotations.BeforeMethod;
056
057 /**
058 * A useful base class which creates a {@link org.apache.camel.CamelContext} with some routes
059 * along with a {@link org.apache.camel.ProducerTemplate} for use in the test case
060 *
061 * @version $Revision$
062 */
063 public abstract class CamelTestSupport extends TestSupport {
064
065 protected static volatile CamelContext context;
066 protected static volatile ProducerTemplate template;
067 protected static volatile ConsumerTemplate consumer;
068 protected static volatile Service camelContextService;
069 private static final Logger LOG = LoggerFactory.getLogger(TestSupport.class);
070 private static final AtomicBoolean INIT = new AtomicBoolean();
071 private boolean useRouteBuilder = true;
072 private final DebugBreakpoint breakpoint = new DebugBreakpoint();
073 private final StopWatch watch = new StopWatch();
074
075 /**
076 * Use the RouteBuilder or not
077 * @return <tt>true</tt> then {@link CamelContext} will be auto started,
078 * <tt>false</tt> then {@link CamelContext} will <b>not</b> be auto started (you will have to start it manually)
079 */
080 public boolean isUseRouteBuilder() {
081 return useRouteBuilder;
082 }
083
084 public void setUseRouteBuilder(boolean useRouteBuilder) {
085 this.useRouteBuilder = useRouteBuilder;
086 }
087
088 /**
089 * Override to control whether {@link CamelContext} should be setup per test or per class.
090 * <p/>
091 * By default it will be setup/teardown per test (per test method). If you want to re-use
092 * {@link CamelContext} between test methods you can override this method and return <tt>true</tt>
093 * <p/>
094 * <b>Important:</b> Use this with care as the {@link CamelContext} will carry over state
095 * from previous tests, such as endpoints, components etc. So you cannot use this in all your tests.
096 *
097 * @return <tt>true</tt> per class, <tt>false</tt> per test.
098 */
099 public boolean isCreateCamelContextPerClass() {
100 return false;
101 }
102
103 /**
104 * Override to enable auto mocking endpoints based on the pattern.
105 * <p/>
106 * Return <tt>*</tt> to mock all endpoints.
107 *
108 * @see org.apache.camel.util.EndpointHelper#matchEndpoint(String, String)
109 */
110 public String isMockEndpoints() {
111 return null;
112 }
113
114 public Service getCamelContextService() {
115 return camelContextService;
116 }
117
118 /**
119 * Allows a service to be registered a separate lifecycle service to start
120 * and stop the context; such as for Spring when the ApplicationContext is
121 * started and stopped, rather than directly stopping the CamelContext
122 */
123 public void setCamelContextService(Service service) {
124 camelContextService = service;
125 }
126
127 @BeforeMethod
128 public void setUp() throws Exception {
129 log.info("********************************************************************************");
130 log.info("Testing: " + getTestMethodName() + "(" + getClass().getName() + ")");
131 log.info("********************************************************************************");
132
133 boolean first = INIT.compareAndSet(false, true);
134 if (isCreateCamelContextPerClass()) {
135 // test is per class, so only setup once (the first time)
136 if (first) {
137 doSetUp();
138 } else {
139 // and in between tests we must do IoC and reset mocks
140 postProcessTest();
141 resetMocks();
142 }
143 } else {
144 // test is per test so always setup
145 doSetUp();
146 }
147
148 // only start timing after all the setup
149 watch.restart();
150 }
151
152 protected void doSetUp() throws Exception {
153 log.debug("setUp test");
154 if (!useJmx()) {
155 disableJMX();
156 } else {
157 enableJMX();
158 }
159
160 context = createCamelContext();
161 assertNotNull(context, "No context found!");
162
163 // reduce default shutdown timeout to avoid waiting for 300 seconds
164 context.getShutdownStrategy().setTimeout(getShutdownTimeout());
165
166 // set debugger
167 context.setDebugger(new DefaultDebugger());
168 context.getDebugger().addBreakpoint(breakpoint);
169 // note: when stopping CamelContext it will automatic remove the breakpoint
170
171 template = context.createProducerTemplate();
172 template.start();
173 consumer = context.createConsumerTemplate();
174 consumer.start();
175
176 // enable auto mocking if enabled
177 String pattern = isMockEndpoints();
178 if (pattern != null) {
179 context.addRegisterEndpointCallback(new InterceptSendToMockEndpointStrategy(pattern));
180 }
181
182 postProcessTest();
183
184 if (isUseRouteBuilder()) {
185 RouteBuilder[] builders = createRouteBuilders();
186 for (RouteBuilder builder : builders) {
187 log.debug("Using created route builder: " + builder);
188 context.addRoutes(builder);
189 }
190 if (!"true".equalsIgnoreCase(System.getProperty("skipStartingCamelContext"))) {
191 startCamelContext();
192 } else {
193 log.info("Skipping starting CamelContext as system property skipStartingCamelContext is set to be true.");
194 }
195 } else {
196 log.debug("Using route builder from the created context: " + context);
197 }
198 log.debug("Routing Rules are: " + context.getRoutes());
199
200 assertValidContext(context);
201
202 INIT.set(true);
203 }
204
205 @AfterMethod
206 public void tearDown() throws Exception {
207 long time = watch.stop();
208
209 log.info("********************************************************************************");
210 log.info("Testing done: " + getTestMethodName() + "(" + getClass().getName() + ")");
211 log.info("Took: " + TimeUtils.printDuration(time) + " (" + time + " millis)");
212 log.info("********************************************************************************");
213
214 if (isCreateCamelContextPerClass()) {
215 // we tear down in after class
216 return;
217 }
218
219 log.debug("tearDown test");
220 doStopTemplates();
221 stopCamelContext();
222 }
223
224 @AfterClass
225 public static void tearDownAfterClass() throws Exception {
226 INIT.set(false);
227 LOG.debug("tearDownAfterClass test");
228 doStopTemplates();
229 doStopCamelContext();
230 }
231
232 /**
233 * Returns the timeout to use when shutting down (unit in seconds).
234 * <p/>
235 * Will default use 10 seconds.
236 *
237 * @return the timeout to use
238 */
239 protected int getShutdownTimeout() {
240 return 10;
241 }
242
243 /**
244 * Whether or not JMX should be used during testing.
245 *
246 * @return <tt>false</tt> by default.
247 */
248 protected boolean useJmx() {
249 return false;
250 }
251
252 /**
253 * Whether or not type converters should be lazy loaded (notice core converters is always loaded)
254 * <p/>
255 * We enabled lazy by default as it would speedup unit testing.
256 *
257 * @return <tt>true</tt> by default.
258 */
259 protected boolean isLazyLoadingTypeConverter() {
260 return true;
261 }
262
263 /**
264 * Lets post process this test instance to process any Camel annotations.
265 * Note that using Spring Test or Guice is a more powerful approach.
266 */
267 protected void postProcessTest() throws Exception {
268 CamelBeanPostProcessor processor = new CamelBeanPostProcessor();
269 processor.setCamelContext(context);
270 processor.postProcessBeforeInitialization(this, "this");
271 }
272
273 protected void stopCamelContext() throws Exception {
274 doStopCamelContext();
275 }
276
277 private static void doStopCamelContext() throws Exception {
278 if (camelContextService != null) {
279 camelContextService.stop();
280 camelContextService = null;
281 } else {
282 if (context != null) {
283 context.stop();
284 context = null;
285 }
286 }
287 }
288
289 private static void doStopTemplates() throws Exception {
290 if (consumer != null) {
291 consumer.stop();
292 consumer = null;
293 }
294 if (template != null) {
295 template.stop();
296 template = null;
297 }
298 }
299
300 public Service camelContextService() {
301 return camelContextService;
302 }
303
304 public CamelContext context() {
305 return context;
306 }
307
308 public ProducerTemplate template() {
309 return template;
310 }
311
312 public ConsumerTemplate consumer() {
313 return consumer;
314 }
315
316 protected void startCamelContext() throws Exception {
317 if (camelContextService != null) {
318 camelContextService.start();
319 } else {
320 if (context instanceof DefaultCamelContext) {
321 DefaultCamelContext defaultCamelContext = (DefaultCamelContext)context;
322 if (!defaultCamelContext.isStarted()) {
323 defaultCamelContext.start();
324 }
325 } else {
326 context.start();
327 }
328 }
329 }
330
331 protected CamelContext createCamelContext() throws Exception {
332 CamelContext context = new DefaultCamelContext(createRegistry());
333 context.setLazyLoadTypeConverters(isLazyLoadingTypeConverter());
334 return context;
335 }
336
337 protected JndiRegistry createRegistry() throws Exception {
338 return new JndiRegistry(createJndiContext());
339 }
340
341 @SuppressWarnings("unchecked")
342 protected Context createJndiContext() throws Exception {
343 Properties properties = new Properties();
344
345 // jndi.properties is optional
346 InputStream in = getClass().getClassLoader().getResourceAsStream("jndi.properties");
347 if (in != null) {
348 log.debug("Using jndi.properties from classpath root");
349 properties.load(in);
350 } else {
351 properties.put("java.naming.factory.initial", "org.apache.camel.util.jndi.CamelInitialContextFactory");
352 }
353 return new InitialContext(new Hashtable(properties));
354 }
355
356 /**
357 * Factory method which derived classes can use to create a {@link RouteBuilder}
358 * to define the routes for testing
359 */
360 protected RouteBuilder createRouteBuilder() throws Exception {
361 return new RouteBuilder() {
362 public void configure() {
363 // no routes added by default
364 }
365 };
366 }
367
368 /**
369 * Factory method which derived classes can use to create an array of
370 * {@link org.apache.camel.builder.RouteBuilder}s to define the routes for testing
371 *
372 * @see #createRouteBuilder()
373 */
374 protected RouteBuilder[] createRouteBuilders() throws Exception {
375 return new RouteBuilder[] {createRouteBuilder()};
376 }
377
378 /**
379 * Resolves a mandatory endpoint for the given URI or an exception is thrown
380 *
381 * @param uri the Camel <a href="">URI</a> to use to create or resolve an endpoint
382 * @return the endpoint
383 */
384 protected Endpoint resolveMandatoryEndpoint(String uri) {
385 return resolveMandatoryEndpoint(context, uri);
386 }
387
388 /**
389 * Resolves a mandatory endpoint for the given URI and expected type or an exception is thrown
390 *
391 * @param uri the Camel <a href="">URI</a> to use to create or resolve an endpoint
392 * @return the endpoint
393 */
394 protected <T extends Endpoint> T resolveMandatoryEndpoint(String uri, Class<T> endpointType) {
395 return resolveMandatoryEndpoint(context, uri, endpointType);
396 }
397
398 /**
399 * Resolves the mandatory Mock endpoint using a URI of the form <code>mock:someName</code>
400 *
401 * @param uri the URI which typically starts with "mock:" and has some name
402 * @return the mandatory mock endpoint or an exception is thrown if it could not be resolved
403 */
404 protected MockEndpoint getMockEndpoint(String uri) {
405 return resolveMandatoryEndpoint(uri, MockEndpoint.class);
406 }
407
408 /**
409 * Sends a message to the given endpoint URI with the body value
410 *
411 * @param endpointUri the URI of the endpoint to send to
412 * @param body the body for the message
413 */
414 protected void sendBody(String endpointUri, final Object body) {
415 template.send(endpointUri, new Processor() {
416 public void process(Exchange exchange) {
417 Message in = exchange.getIn();
418 in.setBody(body);
419 }
420 });
421 }
422
423 /**
424 * Sends a message to the given endpoint URI with the body value and specified headers
425 *
426 * @param endpointUri the URI of the endpoint to send to
427 * @param body the body for the message
428 * @param headers any headers to set on the message
429 */
430 protected void sendBody(String endpointUri, final Object body, final Map<String, Object> headers) {
431 template.send(endpointUri, new Processor() {
432 public void process(Exchange exchange) {
433 Message in = exchange.getIn();
434 in.setBody(body);
435 for (Map.Entry<String, Object> entry : headers.entrySet()) {
436 in.setHeader(entry.getKey(), entry.getValue());
437 }
438 }
439 });
440 }
441
442 /**
443 * Sends messages to the given endpoint for each of the specified bodies
444 *
445 * @param endpointUri the endpoint URI to send to
446 * @param bodies the bodies to send, one per message
447 */
448 protected void sendBodies(String endpointUri, Object... bodies) {
449 for (Object body : bodies) {
450 sendBody(endpointUri, body);
451 }
452 }
453
454 /**
455 * Creates an exchange with the given body
456 */
457 protected Exchange createExchangeWithBody(Object body) {
458 return createExchangeWithBody(context, body);
459 }
460
461 /**
462 * Asserts that the given language name and expression evaluates to the
463 * given value on a specific exchange
464 */
465 protected void assertExpression(Exchange exchange, String languageName, String expressionText, Object expectedValue) {
466 Language language = assertResolveLanguage(languageName);
467
468 Expression expression = language.createExpression(expressionText);
469 assertNotNull(expression, "No Expression could be created for text: " + expressionText + " language: " + language);
470
471 assertExpression(expression, exchange, expectedValue);
472 }
473
474 /**
475 * Asserts that the given language name and predicate expression evaluates
476 * to the expected value on the message exchange
477 */
478 protected void assertPredicate(String languageName, String expressionText, Exchange exchange, boolean expected) {
479 Language language = assertResolveLanguage(languageName);
480
481 Predicate predicate = language.createPredicate(expressionText);
482 assertNotNull(predicate, "No Predicate could be created for text: " + expressionText + " language: " + language);
483
484 assertPredicate(predicate, exchange, expected);
485 }
486
487 /**
488 * Asserts that the language name can be resolved
489 */
490 protected Language assertResolveLanguage(String languageName) {
491 Language language = context.resolveLanguage(languageName);
492 assertNotNull(language, "No language found for name: " + languageName);
493 return language;
494 }
495
496 /**
497 * Asserts that all the expectations of the Mock endpoints are valid
498 */
499 protected void assertMockEndpointsSatisfied() throws InterruptedException {
500 MockEndpoint.assertIsSatisfied(context);
501 }
502
503 /**
504 * Asserts that all the expectations of the Mock endpoints are valid
505 */
506 protected void assertMockEndpointsSatisfied(long timeout, TimeUnit unit) throws InterruptedException {
507 MockEndpoint.assertIsSatisfied(context, timeout, unit);
508 }
509
510 /**
511 * Reset all Mock endpoints.
512 */
513 protected void resetMocks() {
514 MockEndpoint.resetMocks(context);
515 }
516
517 protected void assertValidContext(CamelContext context) {
518 assertNotNull(context, "No context found!");
519 }
520
521 protected <T extends Endpoint> T getMandatoryEndpoint(String uri, Class<T> type) {
522 T endpoint = context.getEndpoint(uri, type);
523 assertNotNull(endpoint, "No endpoint found for uri: " + uri);
524 return endpoint;
525 }
526
527 protected Endpoint getMandatoryEndpoint(String uri) {
528 Endpoint endpoint = context.getEndpoint(uri);
529 assertNotNull(endpoint, "No endpoint found for uri: " + uri);
530 return endpoint;
531 }
532
533 /**
534 * Disables the JMX agent. Must be called before the {@link #setUp()} method.
535 */
536 protected void disableJMX() {
537 System.setProperty(JmxSystemPropertyKeys.DISABLED, "true");
538 }
539
540 /**
541 * Enables the JMX agent. Must be called before the {@link #setUp()} method.
542 */
543 protected void enableJMX() {
544 System.setProperty(JmxSystemPropertyKeys.DISABLED, "false");
545 }
546
547 /**
548 * Single step debugs and Camel invokes this method before entering the given processor
549 */
550 protected void debugBefore(Exchange exchange, Processor processor, ProcessorDefinition definition,
551 String id, String label) {
552 }
553
554 /**
555 * Single step debugs and Camel invokes this method after processing the given processor
556 */
557 protected void debugAfter(Exchange exchange, Processor processor, ProcessorDefinition definition,
558 String id, String label, long timeTaken) {
559 }
560
561 /**
562 * To easily debug by overriding the <tt>debugBefore</tt> and <tt>debugAfter</tt> methods.
563 */
564 private class DebugBreakpoint extends BreakpointSupport {
565
566 @Override
567 public void beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition definition) {
568 CamelTestSupport.this.debugBefore(exchange, processor, definition, definition.getId(), definition.getLabel());
569 }
570
571 @Override
572 public void afterProcess(Exchange exchange, Processor processor, ProcessorDefinition definition, long timeTaken) {
573 CamelTestSupport.this.debugAfter(exchange, processor, definition, definition.getId(), definition.getLabel(), timeTaken);
574 }
575 }
576
577 }