At the heart of unit tests are assertions which provide a mechanism for comparing expected outcomes with actual outcomes. JUnit provides a large selection of overloaded convenience methods that perform predefined logical assertions, such as testing for equality, negations, and conditions specified by a matcher. MUnit also provides a similar set of assertion capabilities such as to assert two values as equals, validate a logical condition, and a variety of other custom assertions that replicate familiar JUnit assertions.
Assert equality
Asserting equality is the simplest validation to set. JUnit provides a method which accepts three parameters, they are the expected value, followed by actual value, and optionally preceded by a message to output on failure.
@Test public void testAssertEquals() { String actual = "animals"; assertEquals("All animals are equal, some more than others", actual, "animals"); }
Fig 1: JUnit test showing how to assert equality.
MUnit offers the equivalent. A simple equality comparison of expected with actual values with an optional message parameter that is output when the test fails.
<flow name="assert-equals"> <set-payload value="animals" doc:name="Set Payload"/> <munit-tools:assert-equals doc:name="Assert equals" actual="#[payload]" expected="animals" message="All animals are equal, some more than others"/> </flow>
Fig 2: MUnit test showing how to assert equality.
Assert that a condition is true
A modification of the equality assertion is the Assert That expression which asserts that an actual value satisfies a condition specified by a matcher. The matcher is provided by the hamcrest libraries. Hamcrest is a framework for writing matcher objects allowing ‘match’ rules to be defined declaratively.
@Test public void testAssertThatHasItems() { assertThat(Arrays.asList("cat", "dog", "hamster"), hasItems("cat", "hamster")); }
Fig 3: Asserts that the array contains, at least “cat” and “hamster.”
MUint replicates similar functionality with the AssertThat processor.
<flow name="assert-that"> <set-payload value='#[["cat","dog","hamster"]]' doc:name="Set Payload" /> <munit-tools:assert-that doc:name="Assert that" is='#[MunitTools::hasItem("hamster")]' expression="#[payload]"/> </flow>
Fig 4: Asserts that the array contains at least “hamster.”
Matchers
MUnit tools provide a set of matchers written as DataWeave functions that provide assertion conditions. They replicate common hamcrest matchers such as String matchers: startsWith, endsWith, containsString, and collection matchers such as everyItem, and hasItem.
Customer assertion and matchers
Some test use cases may not be adequately tested using the out-of-the-box assertions or matchers, which requires a method of creating customer assertion behaviours. This is why Hamcrest provides an abstract class (TypeSafeMatcher) to extend and implement custom matching logic.
public class IsCat extends TypeSafeMatcher<String> { @Override protected boolean matchesSafely(String pet) { try { return pet.equalsIgnoreCase("cat"); } catch (Exception e){ return false; } } @Override public void describeTo(Description description) { description.appendText("Only cats allowed"); } public static Matcher<String> onlyCats() { return new IsCat(); } }
Fig. 5: Custom Hamcrest matcher.
import static com.mulesoft.assertion.IsCat.onlyCats; @Test public void givenAPet_whenIsOnlyCat_thenCorrect() { String pet = "CAT"; assertThat(pet, onlyCats()); }
Fig. 6: Import static method to use as Hamcrest match.
MUnit provides two ways to create customer expressions that provide assertion logic: Assert expression and Run Custom. This may be expressed as a simple DataWeave script that makes a comparison, implemented as a custom DataWeave matcher or as a org.mule.munit.assertion.api.MunitAssertion implementation.
<munit:validation > <set-payload value='#["cat"]' doc:name="Set Payload" /> <munit-tools:assert doc:name="Assert expression" > <munit-tools:that><![CDATA[#[ import * from dw::test::Asserts --- payload must equalTo("cat") ]]]></munit-tools:that> </munit-tools:assert> </munit:validation>
Fig. 7: A custom validation expression written in DataWeave 2.0.
Custom DataWeave matchers
To increase the reusability of assertion logic, custom DataWeave matchers can be created and externalized into a DataWeave script located in test resources directory (src/test/resources).
import * from dw::test::Asserts fun beACat(): Matcher<String> = (actual:String) -> do { var matchesCat = (actual == "Cat") --- { matches: matchesCat, description: {expected: " a cat.", actual: actual} } }
Fig 8: Custom DataWeave matcher CustomMatchers.dwl.
<munit-tools:assert doc:name="Assert expression" > <munit-tools:that ><![CDATA[#[ import * from dw::test::Asserts --- payload must CustomMatchers::beACat() ]]]></munit-tools:that> </munit-tools:assert>
Fig 9: Use the CustomMatchers::beACat() custom matcher.
Custom MUnit assertions
The full power of Java can be unleashed to develop a custom assertion by implementing the org.mule.munit.assertion.api.MunitAssertion interface and execute() method. A dependency on the MUnit::Assert Module is required.
public class CustomAssertion implements MunitAssertion { @Override public void execute(TypedValue exp, Object p) throws AssertionError{ if (!"CAT".equals(exp.getValue())) { throw new AssertionError("Error the payload is incorrect"); } } }
The custom assertion is referenced in the Run Custom event processor.
<munit-tools:run-custom doc:name="Run custom" assertion="com.mulesoft.assertion.custom.CustomAssertion" expression="#[payload]" />
And must also be configured in the mule-artifact.json as an exported package.
{ "name": "mule-application", "minMuleVersion": "4.2.2", "classLoaderModelLoaderDescriptor": { "id": "mule", "attributes": { "exportedPackages": ["com.mulesoft.assertion.custom"] } } }
Conclusion
The assertion capabilities provided by MUnit replicate the most commonly used assertions by developers of JUnit tests. It includes conveniences by predefining assertion types — such as Assert Equals — but also allows the developer to create bespoke assertion testing with the Custom DataWeave Matchers. A Java developer will add ease with the manner in which MUnit provides for assertion testing and thanks to its faithful replication of typical JUnit assertions and HamCrest matchers.
If you are interested in learning more about MUnit and DevOps related activities essential for the implementation of production-ready Mule applications then consider taking our course Anypoint Platform Development: Production-Ready Development Practices (Mule 4).