Mule 4 migration made easy: migrating to MUnit 2

migrating to munit2

It is no secret that migrating to Mule 4 from Mule 3 is a challenge. Mule 4 saw the biggest change in the Mule runtime since its inception. However, with this series of “Mule 4 migration made easy” blogs, I will attempt to soothe any pain you might feel while migrating and provide tips and tricks on how to make the best from Mule 4.

This series will present some how-to guides for migrating some of the most common components and patterns in Mule. Example Mule applications will be used to demonstrate the migration techniques and the code for these applications can be found in the blogs GitHub repository.

MUnit 2.0 framework

In this blog post, I will show what’s changed in Mule 4 and MUnit 2 and how to migrate some common MUnit tasks. I will also look at the changes in the new and improved MUnit 2.0 framework for unit testing your Mule applications:

Main points:

  • Migrating Basic Structure
  • Flow imports
  • Migrating asserts and matchers
  • Migrating mocks and spies
  • Mocking Errors
  • Mocking flows and sub-flows

Below is shown a simple Mule application that works equally in both Mule 3 and Mule 4. I will use it to demonstrate a simple migration. 

A simple Mule application

Let’s start with a simple application that sets the payload to ‘Hello world!’ and then calls another flow to set the payload to ‘Goodbye!’.

The graphical view

simple mule application graphical view

The XML view

The Mule test suite

To test this flow I have written a simple MUnit 1.x (Mule 3) unit test that tests and verifies some of the behavior expected of the simple Mule application. 

The graphical view

mule test suite graphical view

The XML view

The first test invokes the first helloFlow flow and mocks the set-payload payload to return a different value (“Hello world Mock!”) to the value returned by the real flow. It also mocks the flow-ref that refers to the second goodbyeFlow to return the same payload and adds a variable. It spies on the set-payload and verifies that the payload is null before invoking it and verifies that the payload is no longer null after invoking it. The test finishes by asserting the payload is the expected value and asserts the variable has been added.

To conclude the test, it asserts that the expected value is “Goodbye!” and that the variable XXX has been added.

The second test invokes the same helloFlow but mocks an error when the set-payload is called and the test expects an exception, otherwise it fails.

The Mule 4/MUnit 2.x Test suite

Now let’s look at the MUnit 2 equivalent:

The graphical view

munit2 test suite graphical view

The XML view

Migrating Basic Structure

The main structure of the test has stayed the same. Note that both still need an munit:config element to define the test suite and both still use munit:test to wrap each test case with a name and description.

There are some changes within the test case itself. In MUnit 1, mocks, spies, flow execution assertions are all direct children elements of the munit:test element. However, in MUnit 2, they are divided into their own specific parent elements for the part of the test they are executing:

  • <munit:behavior /> for setting up mocks and spies.
  • <munit:execution /> for flow execution or other components for running your actual test.
  • <munit:validation /> for assertions.

Flow imports

In the MUnit 1 example we need to import the exact config files we want to test on:

In MUnit 2, there is no need. All configs are loaded into the context and are accessible to your MUnit test as default.

Migrating asserts and matchers

MUnit 1.0 uses specific methods for assertions. For example in MUnit 1.0

This approach leads to lots of assert processors. MUnit 2 now uses a third-generation style matcher framework. It uses a common assert-that processor with a matcher expression to determine if the test was successful.

Here is an example of the equivalent Mule 4/MUnit 2 asserts:

Key points:

1. All assert-* processors are now replaced by a common assert-that processor.

2. The expression attribute is a DataWeave expression representing the data you want to assert on. This could be the event payload, a variable, the attributes, or anything else a DataWeave expression can resolve.

3. The is attribute is the matcher you want to run against the data. MUnit matchers are a set of DataWeave functions to define assertion conditions for any value in an expression instead of a specific value.

4. When defining matchers, include the prefix MUnitTools:: in the expression: #[MUnitTools::nullValue()].

5. Your assert should now read like a sentence ‘Assert that payload is null’.

6. For a full list of matchers, please review the MUnit Matchers documentation.

Migrating mocks and spies

Mocks

MUnit allows you to mock components in your Mule application to aid unit testing. In the example, there are a few structural changes you need to be aware:

1. The mock namespace is now changed to munit-tools. The munit-tools module now contains the mock event processors and spy processors in one easier to use module.

2. The name of the processor has changed from when to mock-when as there are now other components in the module.

3. The attribute messageProcessor has changed to processor as processors now work at the Event level, not just the message level.

4. In the child node with-attributes the attribute name has changed to attributeName.

5. In the child node then-return the attribute payload must now be a child node.

6. Mule 4 has changed the structure of the Mule Event, Invocation properties are now just call variables in Mule 4. So the child node invocation-properties has changed to variables. Inbound and outbound properties also no longer exist in Mule 4. More info on the Mule 4 Event model is available in the Mule Message Structure documentation.

MUnit 1:

MUnit 2:

7. Session variables have been removed in Mule 4 so there is no replacement.

8. Mule 4 has changed the structure of the Mule Event, inbound and outbound properties have been removed and replaced with attributes. So the child node has now changed to a single attributes node.

MUnit 1:

MUnit 2:

Attributes has a single value attribute. So you need to create a map using DataWeave as shown in the example. For multiple attributes, simply add more entries to the map using the DataWeave expression above.

Spies

Spies mainly contains the same differences as the mock processor (See steps 1-7 above). Additionally mock:assertions-before-call and mock:assertions-after-call are renamed to munit-tools:before-call) and munit-tools:after-call, respectively, which indicate not just assertions are allowed within the scope.

For example, in MUnit 2:

Mocking errors

The second test in the example application mocks and expects error. In Mule 3 and MUnit 1, we had the concept of Exceptions — directly from Java Exceptions and could be mocked separately using a specific mock:throw-an processor:

In MUnit 2, we can use the same munit-tools:then-return element and then specify the error we want to return using munit-tools:error: node.

Notice that Mule 4 does not use the concept of Exceptions. Instead, it uses the Error concept to model any application errors. Here you can specify the exact type of Error you want to throw using the new Mule 4 error syntax, for example, HTTP:BAD_REQUEST to simulate a 400 status code if you are using the http:requester. More details on the new Mule 4 Error concept in the Mule Errors documentation.

To tell the test to expect an error is very similar. Both need to be configured in the main test element, but just need to change from the Exception model to the Error model:

MUnit 1:

MUnit 2:

Mocking flows and sub-flows

In MUnit 1.0/Mule 3, if your application uses a flow-ref to call another flow/sub-flow and you want to mock the flow it calls, you don’t mock or verify the flow-ref message processor itself, but instead, mock the flow or sub-flow that would be invoked by the flow-ref.

In the MUnit1 example we mocked the flow itself:

Whereas in MUnit 2, we need to mock the flow-ref directly:

Now there is no need to worry if you are mocking a sub-flow or a flow. If you change your flow implementation, there might be no need to update your test (maybe in case of error-handlers, etc.) Also, there is no need to use specific matchers for mocking sub-flow names, which was necessary in MUnit 1.

Summary

Those are the basics on migrating MUnit 1 test cases to MUnit 2. 

Key points:

  1. Look for the new structural improvements to test case config and processor config.
  2. Know that processors have changed according to the new Mule 4 Event Model.
  3. Know that Exceptions have changed to Errors in Mule 4 and the associated config has changed accordingly.
  4. It’s now easier to write your assertions.
  5. It’s now easier to mock and spy on flows.
  6. No need to import all your configuration files. 
  7. Happy testing!

Please stay tuned for more “Mule 4 migration made easy” blogs. For more information on Mule 4, check out What’s new with Mule 4?



We'd love to hear your opinion on this post