Over a million developers have joined DZone.

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

· Integration Zone

Learn how API management supports better integration in Achieving Enterprise Agility with Microservices and API Management, brought to you in partnership with 3scale

My very first blog post described how to unit test Mule transformers that access session and/or invocation scoped messge 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:

testFlow

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=<not set>
 correlationGroup=-1
 correlationSeq=-1
 encoding=UTF-8
 exceptionPayload=<not set>
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.

Unleash the power of your APIs with future-proof API management - Create your account and start your free trial today, brought to you in partnership with 3scale.

Topics:

Published at DZone with permission of Ivan K. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}