See how Adaptiv can transform your business. Schedule a kickoff call today

How to mock and spy on processors in MUnit

  • Technical
  • Thought Leadership
  • MuleSoft

We now live in a world of all sorts of online (essential) services to make our daily lives easier – be it weather forecasts, road navigations, music streaming, transferring / paying funds to a party quick & secure … etc. Under the hood those services are usually provided via a network of loosely coupled APIs interconnected together. Individual APIs must provide the exact functionalities exposed around its interface, which is a contract of how requests and responses are passing back-and-forth to perform something useful as a whole. 

For any business following the API-led Connectivity approach, building APIs that are both reusable and purposeful poses a real challenge on Quality-Assurance (QA) testing – particularly when there are frequent APIs iterations to fulfill various business initiatives & objectives. To address this and to simplify testing, as well as offering a mechanism to perform automated testing, it’s preferable to have the ability to test every single API in isolation – this is exactly how MUnit becomes handy in the MuleSoft ecosystem. 

In this post I am going to give an overview of the two most important MUnit processors, namely Mock and Spy, and illustrate how to make use of them to achieve the above-mentioned purpose.  I will also talk about some tips and tricks on improving your development efficiency – when a certain MUnit Test Case is crafted wisely.  This blog serves as a mini HOW-TO for MuleSoft Developers to jumpstart on some practical usage of the out-of-the-box MUnit framework bundled with the MuleSoft development environment.  

Whoever is building MuleSoft APIs shall find this article beneficial. 

What is MUnit, and Why Does it Matter?

MUnit is a Mule application testing framework that provides a full suite of integration and unit testing capabilities. It is fully integrated with Maven to serve your continuous integration / continuous deployment (CI/CD) need – a typical Use Case of an API lifecycle. One core benefit of MUnit is to future-proof your code – avoid introducing breaking changes. 

Without MUnit, developers need to resort to some external tools for unit testing.  Although it serves well for blackbox testing, it requires extra efforts to provide additional mocking endpoints for inbound or outbound interactions, in order to test an API in isolation. This also explains why some inline mocking and spying capabilities of a testing framework is desirable – no extra efforts on building separate mocking (or stub) endpoints that require ongoing maintenance and are typically not self-contained. 

The beauty of a testing framework, such as MUnit, is to allow tapping directly into some internal API logic, so that whitebox testing can be achieved at ease.  It also allows the same codebase to contain both API logic AND testing/validation logic, so that Test Driven Development (TDD) methodology can be put into practice. 

How to create a Test Case using Mock and Spy processors

Let’s talk about the general organisation of test cases within an API: 

  • An API can contain zero or more MUnit Test Suites, residing in one single XML file under the <api_root>/src/test/munit folder. 
  • Each MUnit Test Suite can contain zero or more MUnit Test Cases. 
  • Each MUnit Test Case follows a structure as below: 
    • [Optional] Behaviour – To mock or spy API logic 
    • [Required] Execution – Placing entry point of a flow to execute 
    • [Optional] Validation – To verify behaviour or results 

The following illustrates one MUnit Test Suite named munit-test-suite1, containing two MUnit Test cases MUnit-test-case-normal and MUnit-test-case-bad-request respectively: 

Image 1: Skeleton of a MUnit Test Suite with two Test Cases

Image 1: Skeleton of a MUnit Test Suite with two Test Cases

 

While the structure looks pretty simple, different combinations of the three sections provide surprisingly powerful flexibilities on building sophisticated test cases to serve automated unit testing needs.

Execution

This is the entry point for the starting flow put under test.  It should be required, otherwise the test case performs nothing.

Behaviour

An optional section allowing to put a series of MUnit processors.  In particular the Mock and Spy processor (among others) would go here.  More to come on this.

Validation

After API logic execution, the test case would validate/verify that certain behaviour (e.g. how many times a flow has been invoked) is as expected, or some resulting payload data is matching a certain pattern (e.g. an API responding with a HTTP Status 200 along with a “Success” message content)

 

Mock Processor

The Mock Processor, as its name implies, allows to perform a mock-up of an existing ‘node’ (processor) of the API logic.  Typical example involves mocking a call to another endpoint (e.g. another API) via a http-request connector such that the outcome is replaced instead of actually executing the original node.  The substitution could simply nullify (bypass) that particular node, or substitute a known response with a content pre-stored in a file.  In the latter case, it’s acting as providing a mock endpoint as if the call has been invoked.  This also breaks the dependency of upstream or downstream endpoints, such that a unit test can be done in isolation.

 Below example illustrates a Mock Processor replacing an SFTP Write action with simply returning the payload i.e. not actually performing a Write action.

Image 2: MUnit Behavior sample for Mock processor

Image 2: MUnit Behavior sample for Mock processor

<munit:behavior>

        <munit-tools:mock-when doc:name="sftp:write" doc:id="ed843f16-1c3d-46e8-acc4-0ed3817dc3e6" processor="sftp:write">

               <munit-tools:with-attributes >

                       <munit-tools:with-attribute whereValue="Write content to a file in SFTP for further processing" attributeName="doc:name" />

                       <munit-tools:with-attribute whereValue="ba0aea5b-f4d6-43af-af50-f8633863c323" attributeName="doc:id" />

               </munit-tools:with-attributes>

               <munit-tools:then-return >

                       <munit-tools:payload value="#[payload]" />

               </munit-tools:then-return>

        </munit-tools:mock-when>

        <logger level="INFO" doc:name="Print payload content" doc:id="b5be4b2a-4f4a-4f83-8486-c56c278a5fb6" message="#[payload]"/>

</munit:behavior>

Spy Processor

The Spy Processor, as its name implies, allows spying (inspecting) the internal state when the flow reaches a particular node.  The internal state could be the current payload content, some variable value etc.  It can inspect Before or After executing the candidate node.

Upon inspection, further add-on processing can be performed to alter the contents.  It can simply print out the result via a Logger component for debugging purposes or perform some data tidy-up (such as masking sensitive data elements) before the next Validation step (see the Validation section).

Below example illustrates a Spy Processor that would provide the point-in-time state of the payload immediately after executing the “Map Order details to canonical data model” node:

Image 3: MUnit Behavior sample for Spy processor

Image 3: MUnit Behavior sample for Spy processor

<munit:behavior>

        <munit-tools:spy doc:name="Map Order details to canonical data model" doc:id="78a39faa-e4eb-4534-9020-ce8f6183b64e" processor="ee:transform">

               <munit-tools:with-attributes >

                       <munit-tools:with-attribute whereValue="Map Order details to canonical data model" attributeName="doc:name" />

                       <munit-tools:with-attribute whereValue="afd169ea-6fd6-4767-9959-b856d84b15ea" attributeName="doc:id" />

               </munit-tools:with-attributes>

               <munit-tools:after-call >

                       <logger level="INFO" doc:name="Print Canonical transformation result" doc:id="7cd53619-7f11-49b7-bbca-111dbc7448c7" message="#[payload]" />

               </munit-tools:after-call>

        </munit-tools:spy>

</munit:behavior>

 

Combining Mock and Spy processors form a solid foundation on how a well-defined Test Case can be created. Typically, a test case would have multiple Mock processors for inbound and outbound endpoints, coupled with one or more Spy processors to inspect the intermediate and/or final results.

Making the Best Use of MUnit Test Cases

Remember that Mule Processors (e.g. Logger, Transformation etc) can go into MUnit Test Cases, but MUnit specific Processors cannot go into flow logic.

Because MUnit Test Case are externalised, it allows removing (by Mocking) or adding (by Mocking with some additional logic) to existing API logic WITHOUT altering the actual codebase.  This comes handy in certain use cases, such as when we are porting an API from an older version of Mule platform to a new one, that we don’t want to alter the existing logic.

Imagine a scenario where an API is getting rewritten, while maintaining exact same functionality aka like-for-like. Given that the old code is considered frozen and no additional changes (including debug statements) should be made, a handy method to enrich existing runtime logic is to make use of MUnit externally – within a MUnit test case as an add-on, which serves as the ‘old’ payload for comparison against the new implementation.

Another use case for more efficient unit testing is as follows.  Imagine an API listens for events off an Amazon SQS queue, and then retrieves a file out of Amazon S3 for further processing.  Sample implementation looks like this:

Image 4 - Sample flow to validate via MUnits

Image 4: Sample flow to validate via MUnits

 

Each execution of some manual unit testing would involve putting a file to AWS S3, trigger a message to AWS SQS and then execute the API to listen and then pickup the message to start processing.  It’s doable but may not be the most efficient way.

By crafting a MUnit test case solely for local testing, the entry point can be moved to the processing flow that expects a payload with the file content.  File can then be put locally on the development machine, and feed in using a Set Event node.  In this way it saves time uploading a file to S3 just for sake of testing.  Remember, every bit of such action counts during the development cycle.

Image 5 - MUnit Test Case tricks

Image 5: MUnit Test Case tricks

 

Having said the above, however it’s generally not recommended to use a Mock processor to completely bypass core logic while crafting automated test cases. For example, an API requires to invoke a call to acquire an access token before invoking a subsequent call to a backend. In such case it might be tempting to bypass (skip) acquiring access token in the test case, arguing that it makes a difference only when actually invoking a subsequent backend call – the automated test would be Mocking up the backend call anyway.  Although workable, it may reduce code coverage and risks unintentional break to the API when the logic is subsequently enhanced. A proper approach is to Mock the call to acquire an access token AND to supply a sample response value for validating subsequent processing within the test case.

MUnit Best Practices

  1. It’s desirable to have at least 80% of code coverage for each individual API, so that it covers a majority of API logic during automated testing.
  2. Spy & Mock processors serve equally well under both automated testing scenario and manual testing – observing ‘Isolated/Independent’ principle for Unit Testing [3].
  3. MUnit Test Cases should cover both ‘Happy Paths’, as well as Exception scenarios.

Start Reaping the Benefits Today

In summary, follow three steps to build one MUnit Test Case to validate each path of an API flow:

  1. Execution section invokes the API flow entry-point with appropriate parameters or payload.
  2. Behavior section includes all required Mock operations to decouple any upstream/downstream dependencies, and Spy constructs to inspect in-memory data elements.
  3. Validation section to perform assertions to assure expected outcomes.

I hope you find it easy to jump on creating MUnit test cases for your API, using the Mock and Spy processors as described in this post.  Whether it’s some standard way or some creative way to make your development & testing journey enjoyable and more efficient. The possibilities are endless.

Do practice building enough MUnit test cases for every API being developed, which provides a safety net for introducing breaking changes unintentionally.  The effort spent will prove to be worthwhile for sake of quality assurance.

 


References

[1] Mock Processor https://docs.mulesoft.com/munit/latest/mock-event-processor

[2] Spy Processor https://docs.mulesoft.com/munit/latest/spy-event-processor

[3] Unit Testing Principles https://www.tmap.net/building-blocks/unit-testing-principles/

Ready to elevate your data transit security and enjoy peace of mind?

Click here to schedule a free, no-obligation consultation with our Adaptiv experts. Let us guide you through a tailored solution that's just right for your unique needs.

Your journey to robust, reliable, and rapid application security begins now!

Talk To Us