Mule Unit Tests with Inbound Message Properties in Session or Invocation Scope

By | February 28, 2014
Reading Time: 6 minutes

My very first blog post described how to unit test Mule transformers that access session and/or invocation scoped message properties. To accomplish that I developed a new, better, Mule message. In this post I will describe how this new Mule message class can be extended to allow for session and/or invocation scoped message properties to be set on the message before it is being sent to a Mule flow.

Test Mule Flow

The unit test that will be implemented later will use an extremely simple Mule flow. Graphically, it looks like this:

As you can see, the flow consist of just a request-response VM endpoint and a component that sets a session-scoped property on the messages passing through it. The flow XML looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:vm="http://www.mulesoft.org/schema/mule/vm"
    xmlns="http://www.mulesoft.org/schema/mule/core"
    xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
    xmlns:spring="http://www.springframework.org/schema/beans"
    version="CE-3.4.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-current.xsd

        http://www.mulesoft.org/schema/mule/core
        http://www.mulesoft.org/schema/mule/core/current/mule.xsd

        http://www.mulesoft.org/schema/mule/vm
        http://www.mulesoft.org/schema/mule/vm/current/mule-vm.xsd">
    
    <flow name="testFlow">
        <vm:inbound-endpoint exchange-pattern="request-response" path="vmInbound"/>
        
        <!--
            Copies the value from a certain session scoped property to another
            session scoped property on the received message.
        -->
        <set-session-variable
            variableName="outboundSessionProperty"
            value="#[header:session:inboundSessionProperty]"
            doc:name="Session Variable"/>
    </flow>
</mule>

In the flow XML we can see that the session property set is named “outboundSessionProperty” and it is set to the value of a session property named “inboundSessionProperty”. Aha! We will have to set a session property on the message sent to this flow in order for it to produce a result in the “outboundSessionProperty” session property.

Log4J Properties File

In order to see log generated by Mule, a small Log4J properties file is needed. It is to be named “log4j.properties” and placed in “src/test/resources” in the example project.

log4j.rootLogger=ERROR, out, stdout
# Console appender
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} [%t] %p %m%n
# File appender
log4j.appender.out=org.apache.log4j.FileAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.ConversionPattern=%d{HH:mm:ss,SSS} [%t] %p %m%n
log4j.appender.out.file=mule.log
log4j.appender.out.append=true

Unit Test

The unit test simply creates a Mule message with an arbitrary payload, sets a session property on the message and sends it to the VM endpoint in the test flow. When the response is received, the unit-test verifies that the session property “outboundSessionProperty” has the expected value. For our convenience, the response message is also logged to the console so that we can inspect it.

package com.ivan.mule;

import org.junit.Assert;
import org.junit.Test;
import org.mule.DefaultTestMuleMessage;
import org.mule.api.MuleException;
import org.mule.api.MuleMessage;
import org.mule.api.transport.PropertyScope;
import org.mule.module.client.MuleClient;
import org.mule.tck.junit4.FunctionalTestCase;

/**
 * Tests a Mule flow that require a session property to be set on the ingoing
 * message.
 *
 * @author Ivan Krizsan
 */
public class InboundSessionPropertyTest extends FunctionalTestCase {
    /* Constant(s): */
    /** Value of session scoped property sent and expected. */
    private static final String SESSION_PROPERTY_VALUE = "inboundValue";

    /* (non-Javadoc)
     * @see org.mule.tck.junit4.FunctionalTestCase#getConfigResources()
     */
    @Override
    protected String getConfigResources() {
        return "inboundpropertiesextension.xml";
    }

    /**
     * Verifies that the value in an certain session property set in the inbound
     * message is copied to another session property by the Mule flow.
     * 
     * @throws MuleException If error occurs setting up test.
     */
    @Test
    public void testPropertyPresent() throws MuleException {
        final MuleClient theMuleClient = new MuleClient(muleContext);
        MuleMessage theResponseMuleMessage = null;

        /* Prepare inbound message. */
        final MuleMessage theInboundMessage =
            new DefaultTestMuleMessage("payload", muleContext);
        theInboundMessage.setProperty(
            "inboundSessionProperty", SESSION_PROPERTY_VALUE, PropertyScope.SESSION);

        /* Send message to flow. */
        theResponseMuleMessage =
            theMuleClient.send("vm://vmInbound", theInboundMessage);

        /* Verify result. */
        System.out.println("Received message: " + theResponseMuleMessage);
        final Object theOutboundSessionProperty = theResponseMuleMessage.getProperty(
            "outboundSessionProperty", PropertyScope.SESSION);
        Assert.assertEquals(SESSION_PROPERTY_VALUE, theOutboundSessionProperty);
    }
}

The Old New Message

First we will attempt to use the new, better, message presented in the old blog post. For convenience, I reproduce it here:

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;

/**
 * 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. */
        final Map<String, Object> theInvocationPropertiesMap =
            new HashMap<String, Object>();
        setInvocationProperties(theInvocationPropertiesMap);

        /* Create and set a map holding session properties. */
        final Map<String, Object> theSessionPropertiesMap =
            new HashMap<String, Object>();
        setSessionProperties(theSessionPropertiesMap);
    }
}

Run the Unit Test – First Attempt

If we now run the unit test, we will see the following output on the console:

================================================================================
= Testing: testPropertyPresent                                                 =
================================================================================
23:05:15,943 [Thread-0] ERROR
********************************************************************************
Message               : Expression Evaluator "header" with expression "session:inboundSessionProperty" returned null but a value was required. (org.mule.api.expression.RequiredValueException). Message payload is of type: String
Code                  : MULE_ERROR--2
--------------------------------------------------------------------------------
Exception stack is:
1. Expression Evaluator "header" with expression "session:inboundSessionProperty" returned null but a value was required. (org.mule.api.expression.RequiredValueException)
  org.mule.expression.ExpressionUtils:239 (http://www.mulesoft.org/docs/site/current3/apidocs/org/mule/api/expression/RequiredValueException.html)
2. Expression Evaluator "header" with expression "session:inboundSessionProperty" returned null but a value was required. (org.mule.api.expression.RequiredValueException). Message payload is of type: String (org.mule.api.transformer.TransformerMessagingException)
  org.mule.transformer.AbstractTransformer:139 (http://www.mulesoft.org/docs/site/current3/apidocs/org/mule/api/transformer/TransformerMessagingException.html)
--------------------------------------------------------------------------------
Root Exception stack trace:
org.mule.api.expression.RequiredValueException: Expression Evaluator "header" with expression "session:inboundSessionProperty" returned null but a value was required.
    at org.mule.expression.ExpressionUtils.getPropertyInternal(ExpressionUtils.java:239)
    at org.mule.expression.ExpressionUtils.getPropertyWithScope(ExpressionUtils.java:67)
    at org.mule.expression.ExpressionUtils.getPropertyWithScope(ExpressionUtils.java:50)
    + 3 more (set debug level logging or '-Dmule.verbose.exceptions=true' for everything)
********************************************************************************

Obviously something went wrong and, in addition, we can see that the unit test indeed failed.

The New New Message

Lets add a couple of instance variables and two methods to the DefaultTestMuleMessage class. This is the complete, modified, class:

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;

/**
 * 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.<br/>
 * References to maps holding session and invocation message properties
 * are duplicated in this class since the instance variables in the
 * superclass are private.
 *
 * @author Ivan Krizsan
 */
public class DefaultTestMuleMessage extends DefaultMuleMessage {
    /* Constant(s): */
    private static final long serialVersionUID = -5337792698311698732L;

    /* Instance variable(s): */
    protected Map<String, Object> mSessionPropertiesMap;
    protected Map<String, Object> mInvocationPropertiesMap;

    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. */
        final Map<String, Object> theInvocationPropertiesMap =
            new HashMap<String, Object>();
        setInvocationProperties(theInvocationPropertiesMap);

        /* Create and set a map holding session properties. */
        final Map<String, Object> theSessionPropertiesMap =
            new HashMap<String, Object>();
        setSessionProperties(theSessionPropertiesMap);
    }

    /**
     * Sets the map holding session properties of the message to supplied map.
     * 
     * @param inSessionProperties Session properties map.
     */
    @Override
    void setSessionProperties(final Map<String, Object> inSessionProperties) {
        /*
         * If session properties already set on the message, merge these with the
         * supplied ones in order to be able to set session properties in functional tests.
         */
        if (mSessionPropertiesMap != null) {
            inSessionProperties.putAll(mSessionPropertiesMap);
        }
        mSessionPropertiesMap = inSessionProperties;
        super.setSessionProperties(inSessionProperties);
    }

    /**
     * Sets the map holding invocation properties of the message to supplied map.
     * 
     * @param inInvocationProperties Invocation properties map.
     */
    @Override
    void setInvocationProperties(
        final Map<String, Object> inInvocationProperties) {
        /*
         * If invocation properties already set on the message, merge these with the
         * supplied ones in order to be able to set invocation properties in functional tests.
         */
        if (mInvocationPropertiesMap != null) {
            inInvocationProperties.putAll(mInvocationPropertiesMap);
        }
        mInvocationPropertiesMap = inInvocationProperties;
        super.setInvocationProperties(inInvocationProperties);
    }
}

Note that:

  • There are two new instance variables: mSessionPropertiesMap and mInvocationPropertiesMap. These maps are to hold session and invocation scoped message properties. The reason for adding these instance variables and not using the message properties context in the superclass is that the message properties context is private and cannot be accessed from the child class.
  • There are two new methods, setSessionProperties and setInvocationProperties, that both override methods in the superclass.
    Mules default behaviour is to set maps containing session and invocation properties immediately prior to a message entering a flow, thus these scopes will always be empty.
    The added methods ensure that any properties in respective scope are copied to the new map that is being set.

Run the Unit Test – Second Attempt

If we now run the unit test there should be a green bar indicating success.
In the console we can also see the response message received from the test flow:

================================================================================
= Testing: testPropertyPresent =
================================================================================
Received message: 
org.mule.DefaultMuleMessage
{
 id=9e020e0f-a041-11e3-b5a4-21a8756d83a0
 payload=java.lang.String
 correlationId=
 correlationGroup=-1
 correlationSeq=-1
 encoding=UTF-8
 exceptionPayload=
Message properties:
 INVOCATION scoped properties:
 INBOUND scoped properties:
 MULE_CORRELATION_GROUP_SIZE=-1
 MULE_CORRELATION_SEQUENCE=-1
 MULE_ENCODING=UTF-8
 MULE_SESSION=…
 OUTBOUND scoped properties:
 MULE_CORRELATION_GROUP_SIZE=-1
 MULE_CORRELATION_SEQUENCE=-1
 MULE_ENCODING=UTF-8
 MULE_SESSION=…
 SESSION scoped properties:
 inboundSessionProperty=inboundValue
 outboundSessionProperty=inboundValue
}

Note the session scoped properties at the end of the printout.

5 thoughts on “Mule Unit Tests with Inbound Message Properties in Session or Invocation Scope

  1. Vince

    I tried this but I’m getting an exception:

    Caused by: org.mule.api.transport.NoReceiverForEndpointException: There is no receiver registered on connector “connector.VM.mule.default” for endpointUri vm://vmInbound

    I just copied your flow:

    What I missed?

    Reply
    1. Ivan Krizsan Post author

      Hi!
      I just tried the example in Mule 3.7.0 CE and, with some minor modifications, it does work.
      I modified the value-type of the maps holding the properties in DefaultTestMuleMessage from Object to TypedValue and in the test-class I changed MuleClient to DefaultLocalMuleClient.
      Make sure that the path-attribute of the inbound VM endpoint is set to “vmInbound” without quotes.
      Good luck!

      Reply
  2. amit ghorpade

    good article.
    I have a question – I got a one flow which uses http.query.params values in it and performs some business logic.
    Now When i am writing Munit test case for this flow , how I can pass http.query.params to this flow. I tried with Set Message component in Munit but no luck.

    Reply
  3. Ivan Krizsan Post author

    Hi Amit!
    I belive this is the correct way to do it:
    <munit:test name="myMunitTest" description="Test">
    <munit:set payload="Test payload">
    <munit:inbound-properties>
    <munit:inbound-property key="http.query.params" value="#[['key1' : 'value1', 'key2' : 'value2']]"/>
    </munit:inbound-properties>
    </munit:set>
    <flow-ref name="httpFlow" />
    </munit:test>

    If you use <set-property> in the MUnit test, your properties will end up in the outbound scope, while they should be in the inbound message property scope.
    Happy coding!

    Reply
  4. Siva

    good article.
    I have a question –
    I have one java class and I get Url path as like follows:
    urlPath = eventContext.getMessage().getInboundProperty(“http.request.path”);
    How can pass that value from Junit testing class.

    Reply

Leave a Reply to Ivan Krizsan Cancel reply

Your email address will not be published. Required fields are marked *