Overview

Mule ESB Testing (Part 3/3): System End-to-End Testing with Docker

No Comments

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 incompatible 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 last one in a series of articles about testing Mule ESB projects on all levels (part 1, part 2). It is focusing on the overall system end-to-end test in a Mule project which is performed by setting up the infrastructure with the ESB and a mock server using Docker.

Infrastructure

To perform a system end-to-end test for a Mule application we need at least three different system components:

  • App: First of all we need the Mule application under test.
  • Tester: The testing part performs testing of the application under test. Such testing can be performed by simple tests which perform API calls and verify the results or complex orchestrated calls with a testing tool such as JMeter.
  • Mock: Then we need one or multiple system mocks, which represent the systems the application is dependent on. Mountebank can provide such functionality.

Such a system end-to-end setup would look like this:

system ed-to-end

Docker

Docker is an Open Source technology which allows the virtualization of machines as isolated containers on the host system. It provides the fast and resource efficient creation of containers on a host by using Linux technologies such as cgroups and namespaces. This enables the creation of portable, reproducible and immutable infrastructures. These are a huge bonus for the creation, reproducible execution of test scenarios which include infrastructure.

For a better integration of such a system end-to-end test into a continuous integration pipeline the usage of containerization technology is an advantage. Usage of Docker for example, will allow the fast start up and stopping of an isolated Mule instance with the application and a mock server.

Application under test

Let’s assume for example the following simple scenario. A Mule application provides a REST API on port 8080 which internally calls another backend REST service on port 9000. Such an application could look like that:

Bildschirmfoto 2015-06-09 um 22.24.31

We see in this example an HTTP endpoint which listens on port 8080 and routes all requests to an REST API router. The request to the /myResource will go into the bottom sub flow and will trigger an outbound HTTP call to a server on port 9000. The result is transformed into a string and returned to the caller afterwards. In case of exceptions, an exception strategy will return the approriate result.

We assume we have set up our Mule application already as a single application in a Docker container, as described in this blog post.

Mock server

To allow the Mule application to perform calls to potential backend services in a system end-to-end scenario, a technology such as Mountebank can be used.

Mountebank is an open source tool, which provides cross-platform, multi-protocol test doubles on a network. An application which is supposed to be tested, just needs to point to the IP or URL of a Mountebank instance instead of the real dependency. It allows to test your application through the whole application stack, as you would with traditional stubs and mocks. Supported protocols include HTTP, HTTPS, TCP and SMTP.

For our scenario, the Mountebank imposter would be defined as followed, returning a mocked response on port 9000:

{
  "port": 9000,
  "protocol": "http",
  "name": "My Mock",
  "mode": "text",
  "stubs": [
    {
      "responses": [
        {
          "is":
          {
            "statusCode": 200,
            "headers": {
              "Content-Type": "application/json"
            },
            "body": "{ \"message\": \"You got mocked data\" }"
          }
        }
      ],
      "predicates": [
        {
          "equals": {
            "path": "/anotherResource"
          }
        }
      ]
    }
  ]
}

We assume we have set up our mock server in a Docker container as well, as described in this blog post.

Test Definition

Now to define our test, we use a simple JUnit integration test using the rest-assured library integrated into a Maven build. It’s calling the REST API and verifying that the result is the mocked data from the mock server. At that point calls to the mock server via the Mountebank REST API can be performed for verification purposes as well.

Such a test would look like this:

public class SystemIT {
 
  @Test
  public void testMyResource() {
 
    RestAssured.baseURI = System.getProperty("system.url");
    RestAssured.defaultParser = Parser.JSON;
 
    // Verify an system end-to-end call
    given()
            .param("mimeType", "application/json")
            .get("/api/myResource")
            .then().assertThat()
            .header("content-type", containsString("application/json"))
            .body("message", equalTo("You got mocked data"));
  }
}

Test Configuration

The automation of this scenario is performed by using Maven and the docker-maven-plugin. For that purpose we defined two Docker images, one for the Mule app and one for the mocks server:

<plugin>
  <groupId>org.jolokia</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <version>0.11.5</version>
 
  <configuration>
    <dockerHost>${boot2docker.url}</dockerHost>
 
    <images>
      <!-- Mule app container configuration -->
      <image>
        <name>mule-app</name>
        <alias>mule-app</alias>
        <run>
          <ports>
            <port>${webservice.port}:${webservice.port}</port>
          </ports>
          <links>
            <link>rest-mock:backend</link>
          </links>
          <wait>
            <!-- The plugin waits until this URL is reachable via HTTP ... -->
            <log>Server startup</log>
            <url>${boot2docker.address}:${webservice.port}/api/console</url>
            <time>8000</time>
            <shutdown>500</shutdown>
          </wait>
          <log>
            <prefix>Mule</prefix>
            <date>ISO8601</date>
            <color>blue</color>
          </log>
        </run>
        <build>
          <from>cpoepke/muledocker:latest</from>
          <tags>
            <tag>mule-app</tag>
          </tags>
          <command>/opt/mule-standalone-3.6.1/bin/mule -M-Dbackend.host=$BACKEND_PORT_9000_TCP_ADDR -M-Dbackend.port=$BACKEND_PORT_9000_TCP_PORT</command>
          <assembly>
            <mode>dir</mode>
            <basedir>/</basedir>
            <descriptor>assembly-app.xml</descriptor>
          </assembly>
        </build>
      </image>
      <!-- Backend mock container configuration -->
      <image>
        <name>rest-mock</name>
        <alias>rest-mock</alias>
        <run>
          <ports>
            <port>2525:2525</port>
            <port>9000:9000</port>
          </ports>
          <log>
            <prefix>Mock</prefix>
            <date>ISO8601</date>
            <color>yellow</color>
          </log>
          <wait>
            <!-- The plugin waits until this URL is reachable via HTTP ... -->
            <log>Server startup</log>
            <url>${boot2docker.address}:2525</url>
            <time>2000</time>
            <shutdown>500</shutdown>
          </wait>
        </run>
        <build>
          <from>cpoepke/mountebank-basis:latest</from>
          <tags>
            <tag>rest-mock</tag>
          </tags>
          <command>mb --configfile /mb/imposters.ejs --allowInjection</command>
          <assembly>
            <mode>dir</mode>
            <basedir>/</basedir>
            <descriptor>assembly-mock.xml</descriptor>
          </assembly>
        </build>
      </image>
    </images>
  </configuration>

You will notice in this example the port mapping and Docker links between the two containers.

To start and stop the containers for a test, the following integration-test configuration needs to be set up to configure the Maven phases:

  <!-- Connect start/stop to pre- and
       post-integration-test phase, respectively if you want to start
       your docker containers during integration tests -->
  <executions>
    <execution>
      <id>start</id>
      <phase>pre-integration-test</phase>
      <goals>
        <!-- "build" should be used to create the images with the
             artefacts -->
        <goal>build</goal>
        <goal>start</goal>
      </goals>
    </execution>
    <execution>
      <id>stop</id>
      <phase>post-integration-test</phase>
      <goals>
        <goal>stop</goal>
      </goals>
    </execution>
  </executions>
</plugin>

This will start the Docker containers with docker:start before in the Maven pre-integration-test phase and stop them with docker:stop in the Maven post-integration-test phase.

To execute the integration test we use the failsafe plugin which starts our system end-to-end test in the Maven integration-test phase with our environment variables.

<!-- fails-safe-plugin should be used instead of surefire so that the container gets stopped even
     when the tests fail -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>2.18.1</version>
  <executions>
    <execution>
      <id>integration-test</id>
      <phase>integration-test</phase>
      <goals>
        <goal>integration-test</goal>
      </goals>
    </execution>
    <execution>
      <id>verify</id>
      <phase>verify</phase>
      <goals>
        <goal>verify</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <systemPropertyVariables>
      <!-- Needs to be repeated here (the following two lines strangely doesn't work when the next line is omitted although)
           Maven, you little sneaky beast ... -->
      <!--<system.port>${webservice.port}</system.port>-->
 
      <!-- Map maven variables to system properties which in turn can be used in the test classes -->
      <system.url>http://${boot2docker.ip}:${webservice.port}</system.url>
    </systemPropertyVariables>
  </configuration>
</plugin>
 
<!-- Tell surefire to skip test, we are using the failsafe plugin -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.18.1</version>
  <configuration>
    <skip>true</skip>
  </configuration>
</plugin>

Notice: Do not to forget the port forwarding on Mac and Windows for boot2docker!

Test Execution

The execution of the test and hence the integration into a continuous integration or delivery process can be started by issuing the “mvn verify” command. You see in this log, how all containers are started, the execution waits until they are up, performs the system end-to-end test and how the containers are stopped again:

cpoepke:sys-test cpoepke$ mvn verify
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building System Test - Mule End to End Test Demo 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
...
[INFO] --- docker-maven-plugin:0.11.5:build (start) @ sys-test ---
[INFO] Reading assembly descriptor: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/src/main/docker/assembly-app.xml
[INFO] Copying files to /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/docker/mule-app/build/maven
[INFO] Building tar: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/docker/mule-app/tmp/docker-build.tar
[INFO] DOCKER> Created image [mule-app] "mule-app"
[INFO] DOCKER> Tagging image [mule-app] "mule-app": mule-app
[INFO] Reading assembly descriptor: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/src/main/docker/assembly-mock.xml
[INFO] Copying files to /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/docker/rest-mock/build/maven
[INFO] Building tar: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/docker/rest-mock/tmp/docker-build.tar
[INFO] DOCKER> Created image [rest-mock] "rest-mock"
[INFO] DOCKER> Tagging image [rest-mock] "rest-mock": rest-mock
[INFO] 
[INFO] --- docker-maven-plugin:0.11.5:start (start) @ sys-test ---
[INFO] DOCKER> Starting container 4ee608ab49b9
[INFO] DOCKER> Creating and starting container 4ee608ab49b9 [rest-mock] "rest-mock"
2015-06-09T22:49:36.349+02:00 Mock> mountebank v1.2.122 now taking orders - point your browser to http://localhost:2525 for help
[INFO] DOCKER> Waited on url https://192.168.59.103:2525 and on log out 'Server startup' 2091 ms
[INFO] DOCKER> Starting container b7069c9653cd
[INFO] DOCKER> Creating and starting container b7069c9653cd [mule-app] "mule-app"
2015-06-09T22:49:38.634+02:00 Mule> MULE_HOME is set to /opt/mule-standalone-3.6.1
2015-06-09T22:49:38.642+02:00 Mule> Running in console (foreground) mode by default, use Ctrl-C to exit...
2015-06-09T22:49:38.649+02:00 Mule> MULE_HOME is set to /opt/mule-standalone-3.6.1
2015-06-09T22:49:39.845+02:00 Mule> Running Mule...
...
[INFO] DOCKER> Waited on url https://192.168.59.103:8080/api/console and on log out 'Server startup' 8114 ms
[INFO] 
[INFO] --- maven-failsafe-plugin:2.18.1:integration-test (integration-test) @ sys-test ---
[INFO] Failsafe report directory: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/failsafe-reports
 
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running de.cpoepke.mule.demo.SystemIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.871 sec - in de.cpoepke.mule.demo.SystemIT
 
Results :
 
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
 
[INFO] 
[INFO] --- docker-maven-plugin:0.11.5:stop (stop) @ sys-test ---
[INFO] DOCKER> Stopped and removed container b7069c9653cd [mule-app] "mule-app"
[INFO] DOCKER> Stopped and removed container 4ee608ab49b9 [rest-mock] "rest-mock"
[INFO] 
[INFO] --- maven-failsafe-plugin:2.18.1:verify (verify) @ sys-test ---
[INFO] Failsafe report directory: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/failsafe-reports
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 21.396 s
[INFO] Finished at: 2015-06-09T22:49:50+02:00
[INFO] Final Memory: 22M/206M
[INFO] ------------------------------------------------------------------------

Conclusion

Thorough testing is considered commonly an essential part of good software development practices. Performing this automated and on all levels of the testing pyramid is desired. Hence the issue of testing a mule application end-to-end will come up at some point.

We have shown in this article how a fully automated system end-to-end test infrastructure can be set up. We used for the purpose of testing a Mule application Docker and Mountebank. Nevertheless this test setup can also be reused for other scenarios and application types where an end-to-end test is required.

A full running example of this scenario is available on Github as a demo.

Series

This article is part of the Mule ESB Testing series:

Comment

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