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

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);