Mocking Spring RestTemplate
Just don’t do it.
I’ve seen the following line of code (or it’s corresponding version in other languages) way too many times to be comfortable with it.
val mockRestTemplate = mockk<RestTemplate>()
I think it’s a terrible idea.
Don’t mock what you don’t own.
Why are tests that mock framework elements considered bad?
They don’t help with refactoring
Tests that mock out library components usually cannot be used for refactoring. The main reason for this is because these kind of tests usually “follow the code”. Due to this, we end up having to change test expectations every time you refactor code, even when the logic is unchanged.
As such, a major benefit of doing TDD is thrown right out of the window.
Let me present an example.
Say you have an APIClient
class that makes a GET call to an external endpoint using a RestTemplate
instance contained within in. When testing the APIClient
class, the RestTemplate
is mocked out and expectations are set on it.
every {
template.postForEntity(
"/customers/4520",
any(),
CustomerDetailsResponse::class.java
)
} throws HttpServerErrorException("5xx from server")
The mock is set up so that every POST
call to the /customers/4520
endpoint will simulate the server responding with a 500 Internal Server Error.
The APIClient
code that handles this 5xx response is then tested, using the mocked RestTemplate
instance. All is good for today, the code is checked in, the pipelines are green, time for a pint at the local bar.
However — what happens if for some reason, we change the implementation, but the logic remains the same?
Say we use RestTemplate#exchange
instead of RestTemplate#postForEntity
?
Now we see that we have to update the test expectations, even though there was no change in logic. The tests are no longer a safety net for the change that is about to be made.
Limits a future upgrade
Also — we cannot swap out the library for another one that does the exact same thing, without re-writing the tests. Again, this means there is no safety net for this change, which does not make it any better than not having written tests in the first place.
Assumptions about external code
In the example earlier we assume that the RestTemplate throwing a specific exception implies the remote server responded with a HTTP 500 Internal Server Exception.
This is an assumption about the semantics and the contracts between your application and the library.
If a future upgrade of the library were to change these semantics, there is no safety net anymore, and a bug slips through undetected.
If this assumption was made in many places in the code-base, that the impact is much more.
What’s the alternative?
Mock-servers provided by the library
Most libraries provide a fake implementation of a web-server so that you can test your API client classes.
However, I end up being a bit vary about them, because they are in the end, ‘tied’ to the library. In the event of swapping out the library for another, these kind of tests may not help.
WireMock
This is my preferred approach these days, chosen mostly for the maturity of the tool. Since they are completely independent from implementation detail, I believe they can be more resilient in the time to come.
There are guides you will be be able to find that will detail how to use WireMock in the JVM, and I suggest you have a look if it suits your needs. I’ve added the official Getting Started with JUnit5 reference at the bottom.
Conclusion
Don’t forget value when it comes to tests.
If tests only help us with double entry book-keeping when it comes to writing code, but does not provide a safety net for future change, I believe value is on the lower end of what you can have.
As such, choose to have a more resilient approach to testing API clients.
References
- https://8thlight.com/blog/eric-smith/2011/10/27/thats-not-yours.html
- Dont Mock Types you don’t own https://testing.googleblog.com/2020/07/testing-on-toilet-dont-mock-types-you.html
- Using Wiremock with JUnit 5 http://wiremock.org/docs/junit-jupiter/