Problem
When attempting to unit-test a Mule transformer that need access to session- or invocation-scoped message properties, I found the result to be an IllegalStateException that told me:
Detected an attempt to set an invocation or session property, but a MuleEvent hasn’t been created using this message yet.
I managed to trace the problem to the MessagePropertiesContext class, where I could see that the maps holding the session- and invocation-scoped message properties are created using two classes with names that, in a way, revealed their purpose, namely UndefinedSessionPropertiesMap and UndefinedInvocationPropertiesMap.
I made one attempt at testing such transformers, but was not entirely happy with it. This post describes my second attempt, which I feel more content with.
The technique described here has been tested on Mule Community Edition 3.3 and 3.4, but is expected to work under any 3.x version of Mule.
UPDATE July 27, 2016
The example in this article has been updated to run on Mule CE 3.7 and 3.8. It requires minor modifications to run on earlier 3.x version of Mule CE.
A Mule Transformer
I needed a Mule transformer to test, so I implemented MyTransformer:
package se.ivankrizsan.mule.transformers; import org.mule.api.MuleMessage; import org.mule.api.transformer.TransformerException; import org.mule.api.transport.PropertyScope; import org.mule.transformer.AbstractMessageTransformer; /** * Example transformer that sets properties in the session and invocation * scope. * * @author Ivan Krizsan */ public class MyTransformer extends AbstractMessageTransformer { /** * Transforms supplied message. * * @param inMessage Message to transform. * @param inOutputEncoding Encoding used by the transformer if dealing with text. * @return Transformed message. * @throws TransformerException If error occurred during transformation. */ @Override public Object transformMessage(final MuleMessage inMessage, final String inOutputEncoding) throws TransformerException { inMessage.setProperty("mySessionProperty", "sessionPropertyValue", PropertyScope.SESSION); inMessage.setInvocationProperty("myInvocationProperty", "invocationPropertyValue"); return inMessage; } }
Note that:
- The transformer class inherits from AbstractMessageTransformer.
In order to be able to access message properties in the transformer, the transformer need to receive the entire Mule message to transform, not just the message payload. - The transformer sets two message properties; one in the session scope and another in the invocation scope.
A New, Better Message
When testing a Mule transformer that needs to use one or more message property scopes, we will need to send it a Mule message as discussed above.
Examining the DefaultMuleMessage class, we can see a properties instance variable of the type MessagePropertiesContext.
MessagePropertiesContext is the class that contains the maps that hold the invocation- and session-scoped message properties. In order for my transformer unit test not to cause an exception to be thrown, these maps need to be replaced with instances of, for instance, HashMap. Fortunately, there are two methods in that allows us to do that in the DefaultMuleMessage class, namely setSessionProperties and setInvocationProperties. Less fortunate is the fact that both these methods only have package-visibility.
The trick I use is to create a subclass of DefaultMuleMessage in the same package, namely org.mule, that looks like this:
package org.mule; import java.util.HashMap; import java.util.Map; import javax.activation.DataHandler; import org.mule.api.MuleContext; import org.mule.api.MuleMessage; import org.mule.transformer.types.TypedValue; /** * Mule message that sets regular maps for session and invocation scope property storage. * Only to be used when unit testing transformers without starting a Mule instance. * Note that this class need to be in the org.mule package to be able * to access the methods with which the invocation and session property maps are set. * * @author Ivan Krizsan */ public class DefaultTestMuleMessage extends DefaultMuleMessage { /* Constant(s): */ private static final long serialVersionUID = -5337792698311698732L; public DefaultTestMuleMessage(final MuleMessage inMessage) { super(inMessage); initializePropertiesMaps(); } public DefaultTestMuleMessage(final Object inMessage, final MuleContext inMuleContext) { super(inMessage, inMuleContext); initializePropertiesMaps(); } public DefaultTestMuleMessage(final Object inMessage, final Map<String, Object> inOutboundProperties, final MuleContext inMuleContext) { super(inMessage, inOutboundProperties, inMuleContext); initializePropertiesMaps(); } public DefaultTestMuleMessage(final Object inMessage, final MuleMessage inPrevious, final MuleContext inMuleContext) { super(inMessage, inPrevious, inMuleContext); initializePropertiesMaps(); } public DefaultTestMuleMessage(final Object inMessage, final Map<String, Object> inOutboundProperties, final Map<String, DataHandler> inAttachments, final MuleContext inMuleContext) { super(inMessage, inOutboundProperties, inAttachments, inMuleContext); initializePropertiesMaps(); } public DefaultTestMuleMessage(final Object inMessage, final Map<String, Object> inInboundProperties, final Map<String, Object> inOutboundProperties, final Map<String, DataHandler> inAttachments, final MuleContext inMuleContext) { super(inMessage, inInboundProperties, inOutboundProperties, inAttachments, inMuleContext); initializePropertiesMaps(); } /** * Creates and sets maps holding invocation and session scope * message properties on this message. */ private void initializePropertiesMaps() { /* Create and set a map holding invocation properties. */ Map<String, TypedValue> theInvocationPropertiesMap = new HashMap<String, TypedValue>(); setInvocationProperties(theInvocationPropertiesMap); /* Create and set a map holding session properties. */ Map<String, TypedValue> theSessionPropertiesMap = new HashMap<String, TypedValue>(); setSessionProperties(theSessionPropertiesMap); } }
The implementation of DefaultTestMuleMessage just adds a call to a method that initialize the maps holding the session- and invocation-scoped message properties in each constructor.
Transformer Test Base Class
Having implemented the DefaultTestMuleMessage class is not enough for me, I want it to be as convenient as possible to implement Mule transformer unit tests.
I implemented the following class, based on the Mule test-class AbstractMuleContextTestCase, with the following motivations:
- Availability of a Mule context.Mule messages cannot be created without a Mule context being available.
The context can be empty, as in this case. - Automatic creation of a Mule message to be sent to the transformer under test prior to each test method.
- Easy configuration of the Mule message to be sent to the transformer.
The Mule message is created using the Template Method design pattern, which allows subclasses to override the implementation of one or both of the methods that create the Mule message and that prepares the Mule message by setting payload and message properties on the message. Overriding of the former method is expected to happen less often.
The transformer test base class looks like this:
package se.ivankrizsan.mule.transformertest; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.mule.DefaultTestMuleMessage; import org.mule.api.MuleMessage; import org.mule.tck.junit4.AbstractMuleContextTestCase; /** * Implements a test case for unit testing Mule transformers. * This test case will start an empty Mule context. * No Mule configuration files need to be supplied but a Mule context * will be available.<br/> * Prior to a test method being invoked, a Mule message will be available * in the instance variable mTestMessage. * The Mule message will be initialized as implemented in the method * prepareTestMessage.<br/> * If a test method needs to send more than one message to the transformer * under test, additional Mule messages can be created using the * createEmptyMuleMessage method. * * @author Ivan Krizsan */ public abstract class MuleTransformerUnitTestCase extends AbstractMuleContextTestCase { /** * Mule message to be sent to the transformer under test. * A new instance is created before each test method. */ protected MuleMessage mTestMessage; /** * Default constructor. */ public MuleTransformerUnitTestCase() { /* Use the same Mule context for all test methods in the test. */ setDisposeContextPerClass(true); } /** * Creates a Mule test message and sets common message properties in preparation for a test method. * * @throws Exception If error occurs creating test message or setting common message properties. */ @Before public final void createTestMessageBeforeEachTest() throws Exception { mTestMessage = createEmptyMuleMessage(); prepareTestMessage(mTestMessage); } /** * Creates a Mule message to be sent to the transformer under test. * The message created allow for access of message properties in the session and invocation scopes. * Sets a null payload and no message properties on the message. * Setting of message payload and/or message properties is done in the prepareTestMessage method. * Subclasses should usually not override this method. * * @return Mule message. */ protected MuleMessage createEmptyMuleMessage() { Map<String, Object> theMessageProperties = new HashMap<String, Object>(); /* * Creates a special Mule message which allow for setting and * retrieval of session and invocation message properties. */ MuleMessage theTestMessage = new DefaultTestMuleMessage(null, theMessageProperties, muleContext); return theTestMessage; } /** * Prepares supplied Mule test message before it is being sent to the transformer under test. * Message preparation typically consist of setting a message payload and/or setting message properties. * Called once in preparation for each test method. * * @param inTestMessage Mule message to prepare. * @throws Exception If error occurs during message preparation. */ protected void prepareTestMessage(final MuleMessage inTestMessage) throws Exception { } }
Note that:
- The class MuleTransformerUnitTestCase is abstract.
It is expected that each transformer unit test will create a subclass of this class. - There is an instance variable mTestMessage.
Before execution of each test method in the subclass, this instance variable will hold a reference to a new Mule message that is to be the input to the transformer under test. - The constructor sets the disposeContextPerClass instance variable in the superclass to true.
The consequence of this is that only one single Mule context will be created for all the test methods in a subclass of this class. The reason for creating just one single Mule context for all test methods and not one for each test method is to reduce the execution time of test classes inheriting from this class. - There is a createTestMessageBeforeEachTest method.
This method is responsible for creating the Mule message that is stored in the mTestMessage instance variable.This method is final, so subclasses cannot override it. Instead, it is recommended to override the methods discussed below that this method will delegate the different parts of test message creation to. - There is a method named createEmptyMuleMessage.
This is the method that createTestMessageBeforeEachTest delegates creation of an empty Mule message to. It is the method that creates instances of the DefaultTestMuleMessage class implemented above. Subclasses are not expected to have to override this method, although it is possible. - There is a method named prepareTestMessage.
This is the second delegate method that createTestMessageBeforeEachTest delegates to. In this case, the method is responsible for setting message payload and properties of the test message. Subclasses are expected to override this method.
MyTransformer Unit Test
The prerequisites for the MyTransformer unit test are now in place and the test class can be implemented like this:
package se.ivankrizsan.mule.transformers; import org.junit.Assert; import org.junit.Test; import org.mule.api.MuleMessage; import org.mule.api.transport.PropertyScope; import se.ivankrizsan.mule.transformertest.MuleTransformerUnitTestCase; /** * Tests the Mule transformer implemented in the MyTransformer class. * * @author Ivan Krizsan */ public class MyTransformerTest extends MuleTransformerUnitTestCase { /** * Tests retrieval of properties in the session- and invocation-property * scopes that the transformer is expected to set. * * @throws Exception If error occurs. Indicates test failure. */ @Test public void testPropertyRetrieval() throws Exception { MyTransformer theTransformer = new MyTransformer(); mTestMessage.setPayload("some payload"); MuleMessage theResponse = (MuleMessage) theTransformer.transformMessage(mTestMessage, "UTF-8"); System.out.println(theResponse); Object theSessionPropertyValue = theResponse.getProperty("mySessionProperty", PropertyScope.SESSION); Assert.assertNotNull(theSessionPropertyValue); Object theInvocationPropertyValue = theResponse.getProperty("myInvocationProperty", PropertyScope.INVOCATION); Assert.assertNotNull(theInvocationPropertyValue); } }
Note that:
- The MyTransformerTest class inherit from the class MuleTransformerUnitTestCase that we implemented earlier.
- The MyTransformerTest class overrides the prepareTestMessage method.
The prepareTestMessage method sets the payload and message properties in the message that is to be sent to the transformer under test.
If there would have been multiple test methods in the class, the prepareTestMessage method would have been the appropriate place to set message payload and/or message properties used in all the different test methods.
Payload and/or message properties applicable only to one single test method should be set in that particular method - There is a method named testMessagePropertiesSetByTransformer that is annotated with the @Test JUnit annotation.
This is the one and only test method in this class. - An instance of the transformer under test is created in the testMessagePropertiesSetByTransformer method.
It is created using new, like any POJO. - The test-message is sent to the transformer by invoking its transformMessage method.
Again, no special magic – just a plain method invocation. The message to be transformed is a regular parameter. - Finally, the expected properties are verified in the response message from the transformer.
Run the Unit Test
If we now run the unit test, there should be a green bar indicating test success as well as some console output showing the response message from the transformer:
org.mule.DefaultTestMuleMessage
{
id=bd2d2880-53b8-11e6-a7b2-74d435164ccd
payload=java.lang.String
correlationId=<not set>
correlationGroup=-1
correlationSeq=-1
encoding=UTF-8
exceptionPayload=<not set>
Message properties:
INVOCATION scoped properties:
myInvocationProperty=invocationPropertyValue
INBOUND scoped properties:
OUTBOUND scoped properties:
SESSION scoped properties:
mySessionProperty=sessionPropertyValue
}
I would naturally not print to the console in a real unit test, I did it just to have something to show at the end of this post. 😛
Happy coding!
Thanks for the blog
could you please share git project, that will really help me
I am unable to access the gist links you posted
Thanks for bringing this to my attention!
I have inserted the code into the article and updated it to run on Mule CE 3.7 and Mule CE 3.8.
Happy coding!