How to write unit test?
Test classes hierarchy
- there is modul openhub-test that defines base classes supporting testing. Classes are defined in package org.openhubframework.openhub.test
- base class hierarchy
- classes overview:
- AbstractTest - main parent class for implementing unit tests
- defines Spring profile "test"
- references TestConfig that defines basic configuration for tests
- defines common handy methods
- AbstractDbTest - main parent class for writing tests with database
- extends AbstractTest
- defines Spring profile "h2"Â when H2DB is used for unit tests
- defines handy methods for working with DB
- ActiveRoutes - annotation that is used to declare which Camel route definitions should be active for specific test.
- ActiveRoutesCollector activates only routes which are defined by ActiveRoutes annotation
- AbstractTest - main parent class for implementing unit tests
How to set up tests for new module/project
define configuration specific for the module
@ComponentScan(basePackages = {"org.openhubframework.openhub.modules"}) @PropertySource(value = {"classpath:/config/application-test-default.properties"}) public class ExampleTestConfig { }
There is application-test-default.properties file in core module that contains default values of configuration parameters for unit tests.
define parent classes for unit tests without and with database
@ActiveRoutes(classes = ExceptionTranslationRoute.class) @ContextConfiguration(classes = ExampleTestConfig.class) @ActiveProfiles(profiles = ExampleProperties.EXAMPLE_PROFILE) public abstract class AbstractExampleModuleTest extends AbstractTest { }
@ActiveRoutes(classes = ExceptionTranslationRoute.class) @ContextConfiguration(classes = ExampleTestConfig.class) @ActiveProfiles(profiles = ExampleProperties.EXAMPLE_PROFILE) public abstract class AbstractExampleModulesDbTest extends AbstractDbTest { }
Implement unit tests
See Camel testing for more info how to write unit tests with Camel.
synchronnous routes don't need database => AbstractExampleModuleTest is suitable parent class
@ActiveRoutes(classes = SyncHelloRoute.class) public class SyncHelloRouteTest extends AbstractExampleModuleTest { @Produce(uri = TestWsUriBuilder.URI_WS_IN + "syncHelloRequest") private ProducerTemplate producer; @EndpointInject(uri = "mock:test") private MockEndpoint mock; /** * Checking successful calling. */ @Test public void testSyncHelloRoute() throws Exception { String xml = "<syncHelloRequest xmlns=\"http://openhubframework.org/ws/HelloService-v1\">" + " <name>Mr. Parker</name>" + "</syncHelloRequest>"; String output = producer.requestBody((Object)xml, String.class); SyncHelloResponse res = Tools.unmarshalFromXml(output, SyncHelloResponse.class); assertThat(res, notNullValue()); assertThat(res.getGreeting(), is("Hello Mr. Parker")); } }
asynchronnous routes save messages into dabatase therefore they need DB configuration =>Â AbstractExampleModulesDbTest is suitable parent class
@ActiveRoutes(classes = AsyncHelloRoute.class) public class AsyncHelloRouteTest extends AbstractExampleModulesDbTest { private static final String REQ_XML = "<asyncHelloRequest xmlns=\"http://openhubframework.org/ws/HelloService-v1\">" + " <name>Mr. Parker</name>" + "</asyncHelloRequest>"; @Produce(uri = TestWsUriBuilder.URI_WS_IN + "asyncHelloRequest") private ProducerTemplate producer; @EndpointInject(uri = "mock:test") private MockEndpoint mock; @Test public void testRouteIn_responseOK() throws Exception { getCamelContext().getRouteDefinition(AsyncHelloRoute.ROUTE_ID_ASYNC_IN) .adviceWith(getCamelContext(), new AdviceWithRouteBuilder() { @Override public void configure() throws Exception { TestUtils.replaceToAsynch(this); weaveAddLast().to("mock:test"); } }); mock.expectedMessageCount(1); // action String output = producer.requestBody((Object) REQ_XML, String.class); // verify mock.assertIsSatisfied(); // verify OK response AsyncHelloResponse res = Tools.unmarshalFromXml(output, AsyncHelloResponse.class); assertThat(res.getConfirmAsyncHello().getStatus(), is(ConfirmationTypes.OK)); // check message header Message inMsg = mock.getExchanges().get(0).getIn(); assertThat((String) inMsg.getHeader(AsynchConstants.OBJECT_ID_HEADER), is("Mr. Parker")); } @Test public void testRouteOut() throws Exception { getCamelContext().getRouteDefinition(AsyncHelloRoute.ROUTE_ID_ASYNC_OUT) .adviceWith(getCamelContext(), new AdviceWithRouteBuilder() { @Override public void configure() throws Exception { weaveAddLast().to("mock:test"); } }); mock.expectedMessageCount(1); // action org.openhubframework.openhub.api.entity.Message msg = createAndSaveMessage(ExternalSystemTestEnum.CRM, ServiceTestEnum.ACCOUNT, "testOp", "payload"); producer.sendBodyAndHeader(AsyncHelloRoute.URI_ASYNC_HELLO_OUT, REQ_XML, AsynchConstants.MSG_HEADER, msg); // verify mock.assertIsSatisfied(); // nothing to verify } }
We should test input and output processing at least.
Webservices testing / mocking
TestWsUriBuilder
- implementation of interface org.openhubframework.openhub.api.route.WebServiceUriBuilder
- does support redirecting webservices inbound and outbound calls to defined "direct" component route
- To enable this, annotation @EnableTestWsUriBuilder is provided, meant to be used in scope of testing class
See javadoc of TestWsUriBuilder for futher reference, and following short snippet of replacing inbound call:
@EnableTestWsUriBuilder @ActiveRoutes(classes = AsyncHelloRoute.class) public class AsyncHelloRouteTest extends AbstractDbTest { private static final String REQ_XML = "<asyncHelloRequest xmlns=\"http://openhubframework.org/ws/HelloService-v1\">" + " <name>Mr. Parker</name>" + "</asyncHelloRequest>"; @Produce(uri = TestWsUriBuilder.URI_WS_IN + "asyncHelloRequest") private ProducerTemplate producer; ... // inbound call final String response = producer.requestBody((Object) REQ_XML, String.class);