Mule ESB Testing (Part 1/3): Unit and functional testing - codecentric AG Blog

:

Abstract

As generally acknowledged testing is an important part of the software development process. Tests should be applied during each phase of the software development process from developer tests to acceptance tests. In software engineering comprehensive and automated test suits will secure the quality of software and can provide a safety net for regression and incompatibility changes.

In Mule ESB integration projects these same issues arise. Components used in Mule flows, the flows themselves and the integration of flows in a system context need to be tested thoroughly.

This article is the first one in a series of articles about testing Mule ESB projects on all levels. It is focusing on the smallest components in a Mule project which are tested with unit and functional tests.

Software Testing – The Test Pyramid

Before we dive into the topic we will take a look at the testing context. Ideally testing of software projects is built bottom up. Starting with a large test case base of automated unit tests for the smallest components which make up the whole application together. Going up through architecture layers the number of test cases decreases for larger components because they are compositions of the already tested components. Reaching finally the top of the pyramid where manual supervision or manual tests make up the top of the pyramid testing the application as a whole [1].

automatedtestingpyramid

Source: http://watirmelon.com/2012/01/31/introducing-the-software-testing-ice-cream-cone/
Automated testing pyramid

Unit Tests

On the lowest level unit tests verify the correct functionality of classes. These classes can be in a Mule project simple extensions and customizations of the Mule framework. Examples include:

  • Custom transformers
  • Custom components
  • Custom expression evaluators
  • And in general all the Spring beans that a Mule application will use. Typically in a multi module project these beans are part of a dependency and are tested therefore separately in the dependency built.

Unit tests in a classical sense can test the functionality of custom classes without firing up Mule. A simple POJO class and it’s test case containing customer transformation logic could look like this:

public class CustomerTransformationComponent {
 
   public Map<String, Object> tranformCustomer(Customer customer) {
      Map<String, Object> returnMap = Maps.newHashMap();
      returnMap.put("name", customer.getName());
      // Fields mapping
      // ...
      return returnMap;
   }
}
 
public class CustomerTranformationComponentTest {
 
   @Test
   public testTransform() {
      Customer testCustomer = new Customer();
      // Create test data
      Map<String, Object> customerMap = new CustomerTransformationComponent()
            .tranformCustomer(testCustomer);
      // Assert test data
   }
}

public class CustomerTransformationComponent {public Map<String, Object> tranformCustomer(Customer customer) { Map<String, Object> returnMap = Maps.newHashMap(); returnMap.put("name", customer.getName()); // Fields mapping // ... return returnMap; } }public class CustomerTranformationComponentTest {@Test public testTransform() { Customer testCustomer = new Customer(); // Create test data Map<String, Object> customerMap = new CustomerTransformationComponent() .tranformCustomer(testCustomer); // Assert test data } }

When functionality of custom classes requires a Mule context the Mule Framework provides a Test Compability Kit (TCK) for testing extensions and customizations [3]. For each Mule component type there is an abstract parent class which is derived from org.mule.tck.junit4.AbstractMuleTestCase. They are located in mule-core-3.5.2-tests.jar for Mule version 3.5.2.

For example a Java component implementing the Mule Callable interface with a complex logic relying on the Mule Context can be tested with the aforementioned test classes:

public class CustomerComponent implements Callable {
 
   @Autowired
   public CustomerService service;
 
   @Overwrite
   public Object onCall(MuleEventContext eventContext) throws Exception {
      String customerId = (String) eventContext.getMessage().getPayload();
 
      Customer customer = service.getCustomer(customerId);
 
      Map<String, Object> customerDetails = transformCustomer(customer);
 
      return customerDetails;
   }
}
 
public class CustomerComponentTest extends SimpleJavaComponentTestCase {
 
   @Test
   public testOnCall() {
      // Create test data
      MuleEvent event = getTestEvent(payload, muleContext);
      new CustomerComponent().onCall(new DefaultMuleEventContext(event));
      // Assert test data
   }
}

public class CustomerComponent implements Callable {@Autowired public CustomerService service;@Overwrite public Object onCall(MuleEventContext eventContext) throws Exception { String customerId = (String) eventContext.getMessage().getPayload();Customer customer = service.getCustomer(customerId);Map<String, Object> customerDetails = transformCustomer(customer);return customerDetails; } }public class CustomerComponentTest extends SimpleJavaComponentTestCase {@Test public testOnCall() { // Create test data MuleEvent event = getTestEvent(payload, muleContext); new CustomerComponent().onCall(new DefaultMuleEventContext(event)); // Assert test data } }

These unit tests are beneficial for the following reasons:

  • Components tested with a TCK test case ensure that the common behavior of the component is compatible with the Mule framework.
  • Using a TCK test case allows the developer to concentrate on writing tests for specific behavior of their component.
  • Where testing of a method in the Component API cannot be tested by the TCK test case, the test cases provides an abstract method for the test, ensuring the developer tests all areas of the component.
  • The TCK provides a default test model that is a simple set of test classes. The developer doesn’t need to worry about writing new test classes for their test cases each time. E.g. the Mule lifecycle of a component is automatically tested.

Functional Mule Testing

When it comes to testing the interaction of components between each other in sub flows or “simple” flows functional tests are the recommended way of testing [4]. Because Mule ESB is light weight and easily embeddable in tests the use of the org.mule.tck.junit4.FunctionalTestCase class from the TCK is recommended to test parts or whole flows. This is done by creating a unit test which is derived from this class which will provide an embeddable Mule instance with a Mule context to perform functional tests of these Mule flows.

The emphasis of such tests is to the following aspects of such flows:

  • Functionality of the message flows themselves
  • Validation handling and rule based routing within these flows
  • And their error handling

For example a sub flow which is supposed to be called could look like this:

<sub-flow name="subFlow" doc:name="subFlow">	 	 
 <component class="de.codecentric.example.CustomerComponent" doc:name="Java"/>	 	 
</sub-flow>

<sub-flow name="subFlow" doc:name="subFlow"> <component class="de.codecentric.example.CustomerComponent" doc:name="Java"/> </sub-flow>

To be able to call this sub flow we wrap the call with an VM endpoint and save it in a test resource XML file:

<flow name="TestFlow" doc:name="TestFlow">	 	 
 <vm:inbound-endpoint exchange-pattern="request-response" path="TestFlow" doc:name="VM endpoint"/>	 	 
 <flow-ref name="subFlow" doc:name="Call sub flow for testing"/>	 	 
</flow>

<flow name="TestFlow" doc:name="TestFlow"> <vm:inbound-endpoint exchange-pattern="request-response" path="TestFlow" doc:name="VM endpoint"/> <flow-ref name="subFlow" doc:name="Call sub flow for testing"/> </flow>

The corresponding unit tests could look like this:

public class SubFlowTest extends FunctionalTestCase {
 
   @Test
   public void testFlow() throws Exception{
      MuleClient client = muleContext.getClient();
      String inputPayload = "550e8400-e29b-11d4-a716-446655440000";
      // Create test data
      MuleMessage reply = client.send("vm://TestFlow", inputPayload, null, 5000);
 
      assertNotNull(reply);
      assertNotNull(reply.getPayload());
      // Assert test data
   }
 
    @Override
    protected String[] getConfigFiles() {
        return new String[]{"./src/test/app/sub-flow-test.xml", 
            "./src/main/app/sub-flow.xml"};
    }
}

public class SubFlowTest extends FunctionalTestCase {@Test public void testFlow() throws Exception{ MuleClient client = muleContext.getClient(); String inputPayload = "550e8400-e29b-11d4-a716-446655440000"; // Create test data MuleMessage reply = client.send("vm://TestFlow", inputPayload, null, 5000); assertNotNull(reply); assertNotNull(reply.getPayload()); // Assert test data } @Override protected String[] getConfigFiles() { return new String[]{"./src/test/app/sub-flow-test.xml", "./src/main/app/sub-flow.xml"}; } }

Overwriting the protected String[] getConfigFiles() method provides the test case the required mule config and spring config files. We recommend to split the production xml description and provide test xml configuration in a separat XML file which is only used in specific tests.

This is a simple example how flows can be tested without mocking or changing the tests internally. Mule provides a way to add <test:component/> components in a flow for tests which provides mocking and test functionality. We do not prefer this way because the flow description will be mingled with test information. We recommend to use for such cases the MUnit library which is described in the next blog article.

Testing the (sub) flows using an embedded Mule and with a clean separation between test and production flow description provides the following benefits:

  • Configurations and flows can be tested in isolation from each other which will provide a cleaner separation of tests and reduce the size of each test case. Bugs can be identified this way more focused because they can be localized in explicit test cases.
  • It is not desired to retest Mule standard components because it can be assumed they are already tested thoroughly. Therefore only certain paths and components of flows created by the developers are required for testing.
  • Test cases need to provide an own test infrastructure which is preferably made out of in memory infrastructure components e.g. VM as a transport, ActiveMQ for JMS or H2 as a database. This is necessary because the production environment can not always be provided automated or embedded for a unit test due to license, resource or performance reasons.
  • Reuse between tests e.g. of the in memory infrastructure can be increased by providing the configuration only once for all test cases.

Conclusion

We gave in this blog article an introduction into the first steps in testing Mule applications. Starting by describing how on a lowest architectural layer components and (sub) flows of a Mule application can be tested and which benefit it yields. We described for that purpose classic unit tests with JUnit in the Mule context TCK framework and the functional tests for the TCK. These tests can be found in single module Mule applications or in libraries which contain components and sub flows which are used in multi module Mule applications.

Series

This article is part of the Mule ESB Testing series:

References

[1] http://martinfowler.com/bliki/TestPyramid.html
[2] http://watirmelon.com/2012/01/31/introducing-the-software-testing-ice-cream-cone/
[3] http://www.mulesoft.org/documentation/display/current/Unit+Testing
[4] http://www.mulesoft.org/documentation/display/current/Functional+Testing