In this article, we are going to delve into how to perform integrated tests using MockWebServer with WebClient of Spring. The use of MockWebServer allows us to mock any request made through our WebClient.
MockWebServer acts as a web server, intercepting the made request and returning a pre-established response body.
Main features of MockWebServer
- Testing HTTP Interactions: Spring applications often rely on making HTTP requests to external APIs or services. MockWebServer mocks web server behavior, intercepts HTTP requests, and provides predefined responses for testing different scenarios and HTTP interactions.
- Isolation and Deterministic Testing: By using MockWebServer, you can isolate your Spring application from the actual web services it communicates with during testing. This ensures that your tests are not affected by the availability or reliability of those external services. You have control over the responses returned by the MockWebServer, making your tests deterministic and consistent.
- Flexible Response Configuration: MockWebServer allows you to configure various aspects of the responses, including response codes, headers, and response bodies. This flexibility enables you to simulate different scenarios and edge cases, such as testing error handling or specific response formats, without relying on the actual external services.
- Easy Integration with Spring Testing: You can easily integrate MockWebServer into your Spring tests, whether you use the Spring MVC Test framework or WebClient. We can use MockWebServer alongside other testing frameworks like JUnit or Spock to create comprehensive and reliable tests for your Spring applications involving HTTP interactions.
Overall, MockWebServer provides a convenient and powerful tool for testing the HTTP interactions of your Spring applications, allowing you to create reliable and deterministic tests in a controlled environment.
Using MockWebServer
MockWebServer Dependencies
Let’s add the following dependencies to include the necessary libraries:
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.5.0</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>mockwebserver</artifactId> <version>4.5.0</version> <scope>test</scope> </dependency>
To use the libraries, we have added both the mockwebserver and okhttp dependencies.
Adding MockWebServer to Our Tests
Let’s add MockWebServer to our tests using Spock:
@SpringBootTest(classes = StratioSpringBootService.class) @AutoConfigureMockMvc abstract class ResourceISpec extends Specification { @Autowired protected MockMvc mockMvc static MockWebServer mockBackEnd def setupSpec() { mockBackEnd = new MockWebServer() mockBackEnd.start(37639) } def cleanSpec() { mockBackEnd.shutdown() } }
As we can see in this code snippet, we have added the MockWebServer class, which has been imported as static.
Next, we need to start the MockWebServer before any test is executed, similar to @BeforeAll
, by specifying the port where we want to run it. Finally, the last configuration step would be to shut down the MockWebServer when finish our test.
Next, we add a class that extends the previous abstract class to make use of MockWebServer.
class CarWorkshopISpec extends ResourceISpec { static final CAR_INFO_PATH = "fixtures/carWorkshop/carInfo.json" static final CAR_LOCATION_PATH = "fixtures/carWorkshop/carLocation.json" @Shared String carInfo @Shared String carLocation def setup() { mockBackEnd.setDispatcher(new Dispatcher() { @Override MockResponse dispatch(@NotNull RecordedRequest recordedRequest) throws InterruptedException { if (recordedRequest.getPath().startsWith("/carworkshop/car-info")) { return new MockResponse().setResponseCode(200).setBody(carInfo) } else if (recordedRequest.getPath().startsWith("/carworkshop/car-location")) { return new MockResponse().setResponseCode(200).setBody(carLocation) } } }) carInfo = new ClassPathResource(CAR_INFO_PATH).getFile().getText() carLocation = new ClassPathResource(CAR_LOCATION_PATH).getFile().getText() } def "When a resquest is performed to get car info then a list of cars are returned "() { when: def results = mockMvc.perform(get("/carInfo") ).andReturn().response then: results.status == HttpStatus.OK.value() def expected = "[{\"id\":2,\"code\":\"SAD005\",\"description\":\"Golf red color\",\"address\":Bremen,\"phone\":0067627621,\"postalCode\":00321}]" JSONAssert.assertEquals(new JSONArray(expected), new JSONArray(results.contentAsString), false) } }
In the previous class, we make use of MockWebServer by adding a series of JSON responses through a dispatcher (dispatch method), which depends on the request being made. This is a way to add a response when we have different requests. However, if we have a single request, we can do it directly through enqueue.
Adding a Response to MockServer via enqueue
We can perform an integrated test where we make a single call using enqueue:
mockBackEnd.enqueue(new MockResponse().setBody(carInfo).addHeader("Content-Type", "application/json"))
Adding a Response to MockServer via dispatch
In many cases, we will need to perform an integrated test where we make multiple calls using WebClient. For those cases, it is better to use the dispatch method:
mockBackEnd.setDispatcher(new Dispatcher() { @Override MockResponse dispatch(@NotNull RecordedRequest recordedRequest) throws InterruptedException { if (recordedRequest.getPath().startsWith("/carworkshop/car-info")) { return new MockResponse().setResponseCode(200).setBody(carInfo) } else if (recordedRequest.getPath().startsWith("/carworkshop/car-location")) { return new MockResponse().setResponseCode(200).setBody(carLocation) } } })
Conclusion
This refactoring entry demonstrates adding MockWebServer with WebClient of Spring for integrated testing in our application.
If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!