An alternative approach on using MockRestServiceServer and WireMock in a Spring Boot application
Suppose you are developing a Spring Boot client application that needs to communicate with a remote server API. You are for sure going to write a communication layer (let's call it Repository) between your application and the server, and you are going to test it in some way. So you need to simulate the server behavior i.e. responses and you have some ways to do this in a unit test:
you can use Mockito to mock the RestTemplate
you can use Mockito to mock the Repositories, even if in this case you are not calling the RestTemplate so you overtake some of the application logic
But if you want to load the Spring context and build a sort of integration test, you have these alternatives:
use the @MockBean annotation to mock the Repository response, but also in this case you are not calling the RestTemplate and thus overtaking some of the application logic
start a mock server like WireMock or MockServer and mock the responses there, even if this causes problems if you need to refresh the Spring context, so you need to manage the start/stop/restart phases by yourself
use the Spring's MockRestServiceServer class
I decided to exclude the Mockito approach because I wanted to build an integration test and I had time constraints so I could not spend much time in mocking the RestTemplate responses. So I went for the MockServer solution, also because I wanted to be able to launch the client application on my local machine and be able to open webpages and see some real mock data inside. So, basically I used the same approach both for my test and local profiles. The problem with this is that I needed to have two different configurations for the same bean representing the remote server, and they both had to stay in the src/main/java directory to be able to also run the application on the local machine, for example:
Moreover, to be able to run the MockServer also on the local machine, I needed to have it as a non test scoped dependency in my pom file. This had to be changed, and this post is about the solution I found.
The MockRestServiceServer way
I created this project as an example, so we have two profiles test and production, a repository supposed to communicate with a remote server API and the test class.
So, let's say that we have a ProductRepository that defines a findAll and a findOne methods, like:
And the related test class made like this:
Having @MockserverCondensedTestConfiguration specifying the test intent:
While in my application.properties file I have defined the remote server endpoints, with a the base url as a variable defined in each profiled properties file:
So now, if you try to run the tests, you will get this error:
That's because there is nothing, real nor mock, listening to that endpoint, and responding with something. To mock the response it is possible to use the MockRestServiceServer class, contained in the Spring test dependencies. Let's see an example and how I decided to change the approach of using it in a particular case: the ProductRepositoryTest class becomes like this one:
So for each test, you need to define the mock server expectations, and optionally you can define how many times the remote endpoint gets called in the single test. Tests are now green. This is already a big step ahead compared to the previous approach. But this is an example project, it contains just one repository and one test class with two tests. In my use case I needed to answer these two questions:
what if I need to introduce this approach in an existing application containing several repositories and many test classes?
what if, in addition to repositories, I need to test the upper application layer, i.e. services, which use repositories? And controllers which use services which use repositories?
It became clear that I could not change each test and set expectations in each test. So I came up with a different approach.
The MockRestServiceServer alternative way
Basically I wanted to define the expectations in just one place, like it happened with the MockServer. So I created this class, in the src/test/java directory, that is only active when using the test profile. Moreover I added this class to the one picked up by Spring when loading the context, so the @Bean is configured. Here are the resulting classes:
Of course I also removed the MockRestServiceServer stuff from the test class, so it is exactly as the beginning. This means that there is no need to change the tests already written previously, which, as I wrote before, could be hundreds or thousands. The resulting test class becomes:
Now, if you run the tests, you will... get an error, like this:
This happend because the MockRestServiceServer expectations have an order, that is following the creation and addition order specified in the MockRemoteServerConfiguration definition. You can pass an argument to the MockRestServiceServer build method, to specify which "expectation manager" you want to use. Spring gives you two: SimpleRequestExpectationManager which is the deafult one, and UnorderedRequestExpectationManager. Both count the number of expected calls to the mock server expectation and try to match it with the actual number of calls happened during the test. The difference between the two is that UnorderedRequestExpectationManager ignores the declaration order.
So, if you know how many calls are made to the mock server in the whole test suite, you can try to use UnorderedRequestExpectationManager and specify, for each expectation, the number of expected calls, in the times() method. Unfortunately this is not my case because I don't know how many calls are made to each endpoint and in which order they are made, so I made my own implementation of the AbstractRequestExpectationManager which is actually an extension of the UnorderedRequestExpectationManager.
As you see, if you compare it with the UnorderedRequestExpectationManager reset method, this new one does not do anything, so basically it it saying that we don't care about the order of the calls and we don't care about the number of calls executed. The last things to do are:
changing the declaration of the MockRestServiceServer to build with the NoResetRequestExpectationManager
changing the times(x) to min(1) for each expectation, so we grant that the minimum number of calls to each endpoint is 1
Here we go with the final class:
Tests are now green. Here is the structure of the project at this point:
What we got:
we moved away from the src/main/java directory everything related to tests
we don't have any external server running
we used a Spring component, which is always a good idea in a Spring's application
But we still have a problem, i.e. running the application on the local machine, with mock data, so avoiding the use of a real instance of the remote server. For this I will use WireMock in a docker container.
Using WireMock for mocking responses on the local machine
First thing to do is to pull the WireMock docker image: docker pull rodolpheche/wiremock. Then, of course, we need to create a local profile configuration (application-local.properties), that could be something like:
This means that we intend to run the WireMock on localhost and port 1888. That's why, from the project root, we need to run the docker image with this command:
Now if you go to http://localhost:1888/__admin/ you should see an empty mappings json. And in your project root directory there will be a stub directory containing mappings and __files: the first one will hold the mappings definitions, the second one the response bodies. For example, the mapping file for the findAll products endpoint could be something like this:
And the response, in the __files directory, something like:
Now, if the docker is restarted with docker restart mockserver and you navigate to http://localhost:1888/__admin/, you should see all the mappings defined. How to test that everything will be ok when the application will be running with the local profile? We need a controller to do that (let me skip the service layer and autowire the repository directly):
If you start the application and navigate to http://localhost:8080/products you should see the response coming from the WireMock server on Docker. Here is the resulting structure: