Creating Deep Stubs With Mockito to Chain Method Stubbing
source link: https://rieckpil.de/creating-deep-stubs-with-mockito-to-chain-method-stubbing/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Creating Deep Stubs With Mockito to Chain Method Stubbing
Get an overview of all topics you'll find answers for on this blog here. Also, make sure to grab your cheat sheet PDF copy JUnit 5 & Mockito - Spring MVC - Java EE.
Yet another blog post about a Mockito feature that we should rarely use: Mockito deep stubs. With this article, we'll explore how deep stubbing can reduce the boilerplate stub setup for our tests when chaining fluent APIs of a mocked class. In general, “Every time a mock returns a mock, a fairy dies” should be our guiding principle when working with Mockito. We almost always end up with an exact copy of our implementation when using this feature. This makes our tests brittle and our code harder to refactor.
Please note that I'm not advocating making excessive use of this feature. However, it still worth knowing what the tool we're using (Mockito) provides.
The Starting Point
Fluent APIs are great. Chaining methods of a fluent API make our code more concise as we don't store intermediate results inside a variable. We deal with such fluent APIs when writing code that involves e.g., the Stream
API, a builder pattern, HTTP clients, etc.
However, as soon as we're mocking the class for which we chain multiple methods, stubbing the mock is less trivial and, most of the time, counterproductive.
Let's take a look at an example.
We're going to test an HTTP client class that uses the Spring WebFlux WebClient to fetch a random quote from a remote system:
When we now want to write a unit test for this class, and as long as we're not aware of this Mockito feature and don't know a better alternative (i.e., what the Java Testing Ecosystem offers), our test class becomes a mocking hell.
The Problem With Normal Stubbing
Let's assume we've recently started our Java testing journey and are familiar with JUnit and Mockito. We now want to write a unit test for the InspirationalQuotesClient
and mock any collaborator of our class under test. In this example, that's the WebClient
.
Unfortunately, it's not just the usual Mockito one-liner for this test setup to provide the stubbing setup. Many methods of our mock are invoked, and they're even chained. After some research, several trips to Stack Overflow, and some head crashing, we finally have something working.
JUnit 5 & Mockito Cheat Sheet
Answering 24 questions for the two most essential Java testing libraries.
This brings us to the following test setup for verifying our InspirationalQuotesClient
:
As there is no computational complexity (no if/else no loops, etc.) inside our implementation, the unit test can only verify that whatever .retrieve()
returns, our client is converting to a non-reactive type. We could have gone further and also mock the Mono
but would violate one of the central rules of Mockito to not mock value/data objects.
Consider how this test and especially the stubbing setup grows when we chain further method calls of the WebClient
. We have to provide a stubbing for each particular part inside this chain and exactly match (or use generic ArgumentMatchers
) the usage of the methods. That's quite some work.
Furthermore, we also need some expertise in the class/framework we're using and understand its internals. With the test above, we tightly couple the verification to the internals of the implementation and end up with a brittle test which won't help whenever we refactor our client as each change in the method chain will fail our test.
That's definitely something we don't from our tests. Our tests should back up our refactoring efforts and provide stability.
A Possible Solution: Mockito Deep Stubs
As an alternative, we can use Mockito's deep stubs and refactor our test.
However, this technical and advanced feature doesn't magically make our test better. It just reduces the setup noise:
The important part here is the additional attribute for the @Mock
annotation:
In case we're not using this annotation, we can also instrument Mockito to create a mock that returns deep stubs with the following approach:
The upside of this setup is the reduced boilerplate code to stub the method chaining. We Mockito's deep stubs, we chain the invocation of our mock as it's used inside our class under test (another indicator we're literally copying our implementation).
We can also use ArgumentMatchers
to provide a more generic stubbing setup:
Apart from the obvious downside that this is an (almost) exact copy of our actual class under test, the deep stubs have some further limitations.
First, we can only verify the last mock in the chain and nothing in between:
Next, the deep stubbing won't work whenever one method returns in this method chain return a non-mockable type. That's the case for primitive types (e.g. int
, double
, char
) or final classes (as long as we're not using the InlineMockMaker
).
With those two limitations and the fact that such tests don't support our refactoring efforts, we should rarely use this feature and try to find a better solution for our test.
A Better Solution: MockWebServer
Instead of mocking everything and using deep stubs with Mockito, we can do better.
Another (and better) alternative to testing this particular class is to write a proper HTTP client test. Using the MockWebServer from OkHttp3, we can spin up a local HTTP server in a matter of seconds and stub HTTP responses to test your InspirationalQuotesClient
:
This testing recipe works for any other Java HTTP client.
As a summary and outcome of this article: Keep in mind that Mockito provides a deep stubbing feature. Use it with caution and only if there's no better alternative available.
For more practical Mockito advice, consider enrolling in the Hands-On Mocking With Mockito Online Course to learn the ins and outs of the most popular mocking framework for Java applications.
The source code for this Mockito deep stubbing example is available on GitHub.
PS: Make sure to hurt the least amount of fairies with your tests.
Joyful testing,
Philip
Learn the Painless and Efficient Way to Test Any Java Application
- Testing strategies & best practices
- Testing recipes (database access, HTTP clients, web layer, etc.)
- Independent of your application framework (Spring Boot, Jakarta EE, Quarkus, Micronaut, etc.)
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK