In this article, I would like to showcase some tips & tricks on how to write great unit tests using MUnit - Mule testing framework. MuleESB supports test driven development (TDD) environment in order to build & write unit test cases using MUnit, JUnit & 3rd parties libraries e.g. Mockito.
Software developers are seeking ways to increase the quality of releases, in order to ensure that problems in the code are fixed before they become problems for their customers. One of the approaches is to introduce unit-testing within their environment. This allows MuleSoft developers to test their code “on the fly” and on a regular basis so that problems are isolated within the code and can be inexpensively and easily remedied without impacting on the release cycle.
MUnit is a next generation Mule testing framework. It comes in two flavours:
- Well-known Java approach, where you write your tests as you would normally using JUnit.
- Mule XML code with MUnit schema.
Today, we are going to cover XML-centric details, not the Java way of writing MUnit tests. Nevertheless, everything MUnit can do with XML is possible using Java (and the other way round).
Figure-1: My Sample Project in MuleStudio showing object explorer view
1. Test DRIVEN Development
Test-driven development (TDD) is the heart of agile methodology, so much so that some say you can’t have one without the other. Unit testing has spread beyond the agile development community where it started into the mainstream.
There are many benefits to using TDD. Some are obvious, others are not. Perhaps, the most obvious benefit is that your code will have fewer defects where what the code produced does not match the specifications. A slightly less obvious benefit of TDD is increased code quality. A mature practice of TDD results in developers writing the simplest code possible. This code also tends to be shorter and less complex than code developed by someone not practicing TDD.
Generally speaking, shorter and less complex code indicates higher quality. They are also more readable and understandable which enhances their maintainability.
With this in mind, let's look at some of the most common TDD mistakes I've seen, made and learnt from our projects:
- Not using a Mocking Framework: One of the first things we are taught about TDD is to test things in isolation. This means we have to mock, stub or fake dependencies to isolate methods to be tested.
- Too Much Test Setup: Mocking frameworks make it easier for us to mock dependencies for a class under test, but sometimes it can be too easy.
- Asserting Too Many Elements: Writing a unit test with too many assertions may cause conflict. TDD purists would argue that you should only ever have one assertion per test.
2. Mule - Sample Project with MUnit Test Cases
Suppose, we have a requirement to expose a service via HTTP endpoint, which needs to perform Contact upsert in the Salesforce target system. Also, HTTP endpoint should return a response showing success/failure status. Example configuration will look like:
- Create, then run the example application in Mule Studio.
- Open your Web browser, type localhost:9090/v001/contact in the address bar, then press enter.
- Your browser presents a message that reads, /contact.
- Build the project using 'mvn clean compile package' and run 'mvn test to invokethe MUnit test (Refer: ReadMe.MD file having build & test instructions)
- The main flow (requestCreateContactFlow under file contactUpdates-config.xml) has HTTP endpoint to accept the payload in order to invoke the Salesforce connector service.
- The property-config.xml file has Salesforce connector configuration details. These details are externalize into separate XML file in order to override during the MUnit testing (Refer: mock-config-connectors.xml under the 'src/test/munit' folder).
- Properties files under the folder 'src/main/resources' are externalize for the properties field values like http inbound endpoint URL address - Host/Port and context. These files are replaced by the properties files under the folder 'src/test/resources' for the unit testing purpose.
- Refer MUnit-test-readMe.txt file under the folder 'src/test/resources' having the summary of the MUnit test cases.
- Refer Java class 'MunitInStudio.java'under the 'src/test/java' folder having java program to run the MUnit under the MuleStudio.
3. Writing MUnit Test Cases
- The MUnit test cases are written under the file munit-test-contactUpdates-config.xml (under folder 'src/tst/munit') to perform unit testing using positive/negatibe test scenarios.
- The outbound endpoint is mocked by returning the dummy the payload in order to avoid dependency on Salesforce target system.
- The Salesforce connector configuration & properties files are replaced by the mock/dummy field values (see import statement for property-config.xml and mock-config-connectors.xml)
Import under MUnit
4. Tips for writing great unit tests
- Targeted: Unit tests that test one thing (including one set of inputs) at a time are targeted. The ideal unit-test is one which only examines one function or message processor of the Mule-flow. It’s easier to write a simple unit-test covering one flow using functions of mocks and stubs so that they can isolate flow and create defined inputs for the flow and Message-processor. This allows you to test the output of any piece of code and see if it matches with expected output.
- Isolated: The code you are testing should be isolated from other code in the application as well as any external dependencies or events
- Repeatable & Predictable: A unit test should be capable of being run over and over and assuming that the code under test and the test itself have not changed, producing the same result.
- Independent: Never make your tests dependent on each other. The order of execution should never matter! That makes the tests hard to debug and maintain. There should not be any assumption that your unit tests are going to run in any specific order. Nor should your tests expect or require this.
- Mock out all external services/end-points: It is suggested to mock the external services or endpoint calls in order to avoid overlaps during multiple tests and that different unit tests can influence each other’s outcome.
- Avoid unnecessary preconditions: Avoid having common setup code that runs at the beginning of unit tests. Otherwise, it’s unclear what assumptions each test relies on, and indicates that you’re not testing just a single unit.
- Name your unit tests clearly and consistently: Given a meaningful & business friendly name for each MUnit test in order to related it to the functionality. Also, add a description, comments at the top of each test-case keeping in view of the code maintainability & enhancement support.
- Keep it Simple: It’s also a good idea to implement simplicity in your development methodology. Readability and maintainability will make it easy for the person that takes over the tests after you, to jump in and make the changes. Readable test can also serve as internal documentation for your feature. Less time spent on writing documentation gives you more time to write tests!
The more robust your unit-tests are, the more your developers will see the benefits of the approach. Subsequently, your customers will be able to expect a higher quality finished product.
- Formatting & alignment and naming matters.
- Pay attention to formatting and clean up both the XML and the Flow Editor views of your Mule applications.
- Add comments & description to each test-cases in your MUnit tests keeping in view of the readability & maintenance of of the code.
- Use log4j configuration file under the 'src/test/resources' to capture the output during test phase. This may be helpful for debug and troubleshooting purpose.