Back to Blog Archive

Parameterising Mocks, Spies and Assertions in MUnit tests

Posted on: July 15, 2019
Author:
Stephan

In this article, I’d like to suggest a solution on how to reuse an MUnit test setup with varying test parameters.

When I test a complex flow, there can be a lot of mocking taking place at times, and some of it might not be trivial in nature. The result of my efforts would be a valuable testing array of mocks, spies and tested flows, which is only usable once.

MUnit for external call

Now, in order to test the functionality thoroughly, a test would often need to be repeated with varying input and different respective assertions, trying to catch all equivalence classes and corner cases. Specific bug-related scenarios might be added to that later on. Of course, I could always copy/paste the test, but during development additional steps could show up in the tested flow, data format changes could occur or more precise test requirements could come up, requiring me to change the already multiplied tests.

Even if everything stays nicely put, there could be another problem – mocks occasionally get called several times. For one, this requires mocked content to be handled as a string or byte array since streams would be consumed on the second call. But the functional flow could also repeat the request successfully (e.g. to add three items to a collection, or to test retry behaviour), but getting the same response every time might not be sufficient. To compare: in JUnit-based tests the @Parameterized construct allows us to iterate tests over a collection of test data sets.

How to iterate over a test parameter set in MUnit?

Repeating a test is easy. I just have to put set-message, my functional flow-ref and any following assertions into a subflow and repeat that using, for instance, a for-each scope. In the loop, I can set arbitrary flow variables that my spies and asserts can use to modify their behaviour. The problem is, any mocks will already be initialised when they are called. Due to that, I can’t just refer to my counter variable or payload in thenReturn. The mocked message will be set up only once. That also makes it difficult for mocked MUnit tests to interact with ‘real’ backends, as the mocking occurs before the functional flow is executed.

MUnit for external call

MUnit test subflow

I could also try using multiple mocks for the same call, but since the “when” functionality does not match anything I can set from within the test, this wouldn’t work either.

Parameterisation of a mock is not intended as it seems. I’m out of luck…

Unless I am comfortable with using Java in my project (in the test context at least). Then I could turn the message payload (and properties) into a template. That’s possible using Spring AOP (or other means). Of course, the code and configuration should be included in the test scope only, keeping any production implementation free of it.

Note: the above code snippet has been shortened to show the concept. You’ll find a more complete implementation at the end of this post, including:

  • support for both full replacements (returned as their return type) and a plain payload containing MEL expressions (returned as String)
  • templating of properties, so you can return HTTP.status = 404 on certain IDs etc.
  • unchanged payload/property when no expression was evaluated.
  • filtering based on a MEL expression that needs to resolve to true for any payload evaluation to be performed at all.

The configuration needed in the Mule test XML:

What do I gain from substituting a custom MessageProcessorBehavior?

Now it is possible to include MEL expressions in my test data payload, which will be evaluated when the functional flow executes, allowing me to reference any present variables or functions to modify or replace test data on each call.

For repeated calls to the different payload, I can also use the template as a redirect to other test data files, so that call one returns file one etc.

1 #["#[getResource('sample_data/responses/create-store-item-response-from-system-api-'
+ flowVars.currentTest + '.json').asStream()]"]

Since the payload is reevaluated on the actual request, I will also be able to return mocked streams several times, which is closer to real-life behaviour than using strings.

Some things will be lacking though:

  • the entire loop will qualify as one test. Either successful or not. I need to provide enough logging to check which iteration was causing the actual failure. It could also make sense to keep a couple of redundant tests for different result groups (like errors).
  • Since it is possible to do complex things in MEL, test weight might shift towards my test implementation. I should not try to generate too interactive test data here, as an error will not decisively point out an implementation problem in my functional flow any more.
  • The mocked message payload will be read into a string in order to evaluate it as an expression. I’ll need to provide filtering to make sure that large-bore payloads are skipped.

Full implementation

Alternatives

Recently I found this: https://profit-online.pl/2018/12/externalize-scripts/. The gist of the matter is to use *.dwl files as payload, which would be evaluated when being set as payload. I haven’t tried that yet in Mule 3, but it would be a strong alternative, especially for non-java environments.

Conclusion

  • It is possible (albeit a little complicated) to get different results from a mock on each call and spy/assert accordingly.
  • That way, tests can be repeated with test data sets
  • Or flows containing repeated requests can be tested using realistic behaviour, and stream based payload.
Author:
Stephan

One Comment for “Parameterising Mocks, Spies and Assertions in MUnit tests”

  1. Harshit says:

    Hi,

    Could you please help me on how to mock invoke component. In my project, Invoke component is internally calling some database initialization class.When I am mocking mocking invoke component, It should not go in java classes. I must simply return the message with payload.

    Thanks.

Comments

Contact Us

Ricston Ltd.
Triq G.F. Agius De Soldanis,
Birkirkara, BKR 4850,
Malta
MT: +356 2133 4457
UK: +44 (0)2071935107

Send our experts a message

Need Help?
Ask our Experts!