Unit Testing with the Validations Module in Mule ESB

By | July 1, 2016

In this post I will show a workaround for a bug that appears when testing Mule flows that contains one or more validators from the relatively new Validations Module. The bug has been verified to be present in both Mule ESB 3.7.0 and 3.8.0.
There are two issues in MuleSofts JIRA here and here, but the fix has only been applied to the DefaultMuleApplication class and thus won’t improve the situation when implementing tests using the class FunctionalTestCase as a parent class.

Create the Example Project

The example project, which is a regular Maven project, will first reproduce the bug and then propose a workaround.
Create an empty Maven project in your favourite IDE without using a Maven archetype. I have named my project “validator-unittesting”.

The pom.xml File

Replace the contents of the pom.xml file with the listing below. Note that this pom-file will not allow for more than running tests in the project.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>se.ivankrizsan.mule.validatorunittesting</groupId>
    <artifactId>validatorunittesting</artifactId>
    <packaging>mule</packaging>
    <version>1.0.0-SNAPSHOT</version>

    <description>Shows how to work around a bug in Mule ESB when unit-testing a configuration that uses the validation-module.</description>

    <properties>
        <mule.version>3.8.0</mule.version>
        <jdk.version>1.7</jdk.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>${jdk.version}</source>
                    <target>${jdk.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.mule</groupId>
            <artifactId>mule-core</artifactId>
            <version>${mule.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mule.modules</groupId>
            <artifactId>mule-module-spring-config</artifactId>
            <version>${mule.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.mule.transports</groupId>
            <artifactId>mule-transport-vm</artifactId>
            <version>${mule.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.mule.modules</groupId>
            <artifactId>mule-module-xml</artifactId>
            <version>${mule.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mule.modules</groupId>
            <artifactId>mule-module-client</artifactId>
            <version>${mule.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mule.modules</groupId>
            <artifactId>mule-module-validation</artifactId>
            <version>${mule.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.mule.tests</groupId>
            <artifactId>mule-tests-functional</artifactId>
            <version>${mule.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mockobjects</groupId>
            <artifactId>mockobjects-core</artifactId>
            <version>0.09</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>muleforge-repo</id>
            <name>MuleForge Repository</name>
            <url>http://repository.muleforge.org</url>
            <layout>default</layout>
        </repository>
    </repositories>
</project>

Mule Configuration File

To make it easy for myself, I have placed the Mule configuration file used by the test in src/test/resoruces/mule-config.xml. It contains the following configuration:

<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns="http://www.mulesoft.org/schema/mule/core"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:http="http://www.mulesoft.org/schema/mule/http"
      xmlns:validation="http://www.mulesoft.org/schema/mule/validation"
      xmlns:test="http://www.mulesoft.org/schema/mule/test"
      xmlns:vm="http://www.mulesoft.org/schema/mule/vm"
      xsi:schemaLocation="
        http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
        http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
        http://www.mulesoft.org/schema/mule/validation http://www.mulesoft.org/schema/mule/validation/current/mule-validation.xsd
        http://www.mulesoft.org/schema/mule/test http://www.mulesoft.org/schema/mule/test/current/mule-test.xsd
        http://www.mulesoft.org/schema/mule/vm http://www.mulesoft.org/schema/mule/vm/current/mule-vm.xsd">

    <description>
        Shows how to work around a bug in Mule ESB when unit-testing a configuration that uses the validation module.
    </description>

    <flow name="main">
        <vm:inbound-endpoint path="test" exchange-pattern="request-response"/>

        <test:component logMessageDetails="true"/>

        <validation:is-not-null value="#[payload]" message="#[string:Payload must not be empty]"/>
    </flow>
</mule>

Note that:

  • I have used a VM inbound endpoint in the main flow.
    This was since it is easier to send a request with a payload to a VM endpoint using the Mule client.
  • There is a <test:component> element.
    This will log all received request messages, including their metadata, to the console.
  • There is a <validation:is-not-null> element.
    This is a validation from the validations module that asserts that the expression in the value attribute does not evaluate to null.
    If the expression does evaluate to null, then a ValidationException will be thrown with the message from the message attribute of the <validation:is-not-null> element set as the exception message. In this case it is the payload of the received request that must not be null.

Log4J Configuration

A minimal Log4J2 configuration file was created in src/test/resources/log4j2-test.xml with the following contents just to get some logging output to the console while running the tests.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <Console name="STDOUT" target="SYSTEM_OUT">
            <PatternLayout pattern="%d %-5p %-30C - %m%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="org.mule" level="info"/>
        <Logger name="org.mule.transport" level="warn"/>
        <Root level="warn">
            <AppenderRef ref="STDOUT"/>
        </Root>
    </Loggers>
</Configuration>

Unit-Test Class

The unit-test class was placed in src/test/java and is implemented as this:

package se.ivankrizsan.mule.validatorunittesting.validatorunittesting;

import org.junit.Before;
import org.mule.DefaultMuleMessage;
import org.mule.api.MuleMessage;
import org.mule.api.client.MuleClient;
import org.mule.api.config.ConfigurationBuilder;
import org.mule.config.builders.ExtensionsManagerConfigurationBuilder;
import org.mule.tck.junit4.FunctionalTestCase;
import org.mule.transport.NullPayload;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

/**
 * Test running a unit-test with a Mule configuration file that uses the validation module.
 *
 * @author Ivan Krizsan
 */
public class ValidatorUnittestingTestCase extends FunctionalTestCase
{
    /* Constant(s): */
    protected static final String TEST_INBOUND_ENDPOINT_NAME = "vm://test";

    /* Instance variable(s): */
    protected MuleMessage mPostReqeust;
    protected MuleClient mMuleClient;

    /**
     * Performs set up before each test.
     */
    @Before
    public void setUpBeforeTest() {
        mPostReqeust = new DefaultMuleMessage("Test message", muleContext);
        mMuleClient = muleContext.getClient();
    }

    @Override
    protected String getConfigFile()
    {
        return "mule-config.xml";
    }

    /**
     * Tests sending a request with a non-empty payload.
     * Expected outcome: A response message should be received.
     *
     * @throws Exception If an error occurs. Indicates test failure.
     */
    @Test
    public void nonEmptyPayloadTest() throws Exception
    {
        final MuleMessage theResponseMessage = mMuleClient.send(TEST_INBOUND_ENDPOINT_NAME, mPostReqeust);

        assertNotNull(theResponseMessage);
        assertNull(theResponseMessage.getExceptionPayload());
        assertFalse(theResponseMessage.getPayload() instanceof NullPayload);
    }

    /**
     * Tests sending a request with an empty payload.
     * Expected outcome: A response message with an exception payload should be received.
     *
     * @throws Exception If an error occurs. Indicates test failure.
     */
    @Test
    public void emptyPayloadTest() throws Exception
    {
        mPostReqeust.setPayload(null);
        final MuleMessage theResponseMessage = mMuleClient.send(TEST_INBOUND_ENDPOINT_NAME, mPostReqeust);

        assertNotNull(theResponseMessage);
        assertNotNull(theResponseMessage.getExceptionPayload());
    }
}

There are two tests in this test-class; one that sends a request with a payload and another that sends a request with a null payload.

Reproducing the Bug

If we now run the test-class, both tests will fail with console output similar to this:

Caused by: java.lang.IllegalStateException: Could not obtain the ExtensionManager
	at org.mule.util.Preconditions.checkState(Preconditions.java:38)
	at org.mule.module.extension.internal.config.ExtensionsNamespaceHandler.init(ExtensionsNamespaceHandler.java:64)
	at org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver.resolve(DefaultNamespaceHandlerResolver.java:131)
	at org.mule.config.spring.MuleHierarchicalBeanDefinitionParserDelegate.parseCustomElement(MuleHierarchicalBeanDefinitionParserDelegate.java:85)
	at org.mule.config.spring.MuleHierarchicalBeanDefinitionParserDelegate.parseCustomElement(MuleHierarchicalBeanDefinitionParserDelegate.java:140)
	at org.mule.config.spring.MuleHierarchicalBeanDefinitionParserDelegate.parseCustomElement(MuleHierarchicalBeanDefinitionParserDelegate.java:140)
	at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1417)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:180)
	at org.mule.config.spring.MuleBeanDefinitionDocumentReader.parseBeanDefinitions(MuleBeanDefinitionDocumentReader.java:56)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:144)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:100)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:510)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:392)
	... 31 more

This looks very similar to the log output in the issue MULE-8487, which led me to believe that the extensions manager had also been omitted when starting an embedded instance of Mule in a test.

Workaround

I propose a workaround for the bug that consists of adding the following method to the test-class:

    @Override
    protected void addBuilders(List<ConfigurationBuilder> builders) {
        super.addBuilders(builders);
        builders.add(0, new ExtensionsManagerConfigurationBuilder());
    }

Note that the extensions manager builder is inserted first in the list of builders. This is because the extensions manager must be created before the Spring context is created, since the creation of the Spring context depends on an extension manager instance being available, since there is a validator in the Mule configuration file.

Successful Test

If the test-class is run again, both the tests should now succeed.

Happy coding!

Leave a Reply

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