🔥 Real-World Examples: Explore Our Salesforce & ManoMano Case Studies! 🔥 Read Now

Testing Exception Handling of Spring's REST Controllers

18.12.2021 Manuel Gerding - 6 min read
Testing Exception Handling of Spring's REST Controllers

This blog post gives you an effortless way to test whether your exception handling of Spring Boot’s RestTemplate is working. We will use chaos experiments in Steadybit so we don’t need to spend time mocking a part of your system or conducting cumbersome manual testing.

Introducing Spring Boot’s RestTemplate

Microservices and distributed applications are a rising trend in today’s software development practices. At some point, two microservices will have the need to communicate with each other. The simplest approach to achieve that is to use HTTP-based communication like REST (Representational State Transfer).

To do that, we use the RestTemplate in Spring Boot as shown in the Listing below. In this example, we reach an endpoint given as parameter (url) via HTTP GET and expect a list of type Product in the response body. This response is used in an orchestrated endpoint reachable via /products/ and collects various products from endpoints of different microservices (more context of the demo online shop can be found here).

@RestController
@RequestMapping("/products")
public class ProductsController {
   @Value("${rest.endpoint.hotdeals}")
   private String urlHotDeals;
   ...

   @GetMapping
   public Products getProducts() {
      Products products = new Products();
      products.setFashion(getProduct(urlFashion));
      products.setToys(getProduct(urlToys));
      products.setHotDeals(getProduct(urlHotDeals));
      return products;
   }

   private List getProduct(String url) {
       return restTemplate.exchange(
          url,
          HttpMethod.GET,
          null,
          productListTypeReference
       ).getBody();
   }
}

Well, obviously, this code is very simplified and in reality, we also need to think about the error case. What if the other system is not available? Or answers slowly? What if the response is different than expected? Or erroneous? How to verify that without changing the other system’s source code?

Right now, the code will throw an (unhandled) exception. To be specific, a HttpClientErrorException in case of HTTP 4xx responses and a HttpServerErrorException in case of HTTP 5xx responses. In order to handle the exception appropriately, Spring Boot offers multiple ways – as shown e.g. in the blog post of Baeldung about “Spring RestTemplate Error Handling”.

Testing Your Unmodified Code First

So, while the above-described solution works, we haven’t considered the error handling until now. Before we code, let’s follow the paradigm of Test Driven Development and test it first for real – without mocking or temporary code changes.

Luckily, Steadybit can change the behavior of a Java application at runtime via bytecode injection, without re-deploying anything and without the requirement of additional source code dependencies. Once the agents are installed, Steadybit has covered the application as visible on our dashboard (see Figure below), and we can start with our first tests on unhandled exceptions.

As Steadybit has already discovered our system, we can easily create a few experiments to check on the behavior. We start with testing what happens if one of the called endpoints (e.g. hotdeals at ${urlHotDeals}/products) throws an exception.

Step 1: Create an Application Experiment

The first thing to do is to create and define a new experiment to provoke an erroneous endpoint. When using Steadybit, this is pretty straightforward:

  1. We go to Experiments and choose to create a new Application Experiment (because we are injecting chaos on the level of the Java Virtual Machine).
  2. We give the experiment a useful name (e.g., “Gateway can handle Hot-Deals exceptions”) and choose the Global area for now.
  3. We choose to attack the hotdeals application, as shown below.

Create an Application Experiment

  1. Since we want a maximum effect, we choose to have an impact of 100% at the following wizard step.
  2. We apply the “controller exception” attack at the next step to provoke an exception at GET-requests on the /products-endpoint of hotdeals (see Figure below).

Create an Application Experiment 1

Finally, we can use the HTTP Status check of the monitoring section to verify that the Gateway’s endpoint of http://k8s.demo.steadybit.io/products always responds with an HTTP OK status within a timeout of 3 seconds (see Figure below). This is our desired behavior of always having products at the gateway even when one product-microservices fails. Create the experiment by finalizing the wizard via “Save”.

Create an Application Experiment 2

Step 2: Run the Experiment to Check the Behavior

Now it’s time to verify the status quo. By starting the experiment, we can see what happens without the need to change anything in the source code or shut down parts of the system (in this case, hotdeals).

Run the Experiment to Check the Behavior

Well, okay, that was expected. The exception injected by Steadybit in Hot-Deals leads to an erroneous response of the RestController-endpoint (see logs below). This affects Gateway’s current implementation by returning an HTTP 500 (see Figure above). So, by injecting an exception at component 1 (Hot-Deals) and stopping it from working, we also caused an exception at the dependent component 2 (Gateway).

2021-06-15 17:35:19.389 ERROR Request processing failed; nested exception
is: java.lang.RuntimeException: Exception injected by steadybit at com.
steadybit.HotDealsRestController.getHotDeals(HotDealsRestController.java:28)
...
021-06-15 17:35:55.891 ERROR 500 Server Error for HTTP GET "/products"
org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : [{"timestamp":"2021-06
15T17:35:55.888+00:00","status":500,... (5526 bytes)]
...

Step 3. Implement Exception Handling

Our goal is to fix the current implementation by adding proper exception handling and validate it via the created experiment afterward. There are two general places to work on the issue: The first one is to fix the code at Hot-Deals to be able to provide a fallback instead of an HTTP 500, and the second one is to fix the code at Gateway to be able to work with erroneous product-responses and provide a proper fallback. We decide to fix it in the Gateway by applying the simplest solution possible: a try-catch-block, as seen in the Listing below.

private List<Product> getProduct(String url) {
   try {
      return restTemplate.exchange(
         url,
         HttpMethod.GET,
         null,
         productListTypeReference
      ).getBody();
   } catch (RestClientException e) {
       log.error("RestClientException while fetching products", e);
       return Collections.emptyList();
   }
}

Step 4. Validate the Expected Impacts

Now that we have improved the implementation, we need to check whether it works as expected. For that, we reuse the existing experiment and re-execute it. This time the experiment is successful since the /product-endpoint always returns an HTTP OK with a valid JSON. When hotdeals is erroneous, an empty list replaces it’s content.

Validate Improvement

Conclusion

In this blog post, we tested and improved the error handling for a synchronous HTTP request. By using Steadybit, we could verify the different error states without changing any source code or shutting down parts of the system. However, the current implementation is still very simplistic. In a real-world use case, you may need a more advanced solution, for instance, a circuit breaker. Thereby, the gateway component reduces the number of requests forwarded to hotdeals and, thus, the load on hotdeals. This is especially desirable when the reason of hotdeals’ exception is related to increased load. Check out the implementation of that endpoint in GitHub and verify it with the created experiment.

If you want to get started with Steadybit to run your own chaos experiments, you can sign up for a free trial or request a demo.