From 30c56cc983eb49b37a8fe009f8eac8239345b916 Mon Sep 17 00:00:00 2001
From: Kai Moritz
Date: Sat, 3 Oct 2020 16:49:23 +0200
Subject: [PATCH] Trimmed the example to showcase an unit-test for a simple
exception-handler
---
pom.xml | 15 --
.../java/de/juplo/demo/ExampleController.java | 34 +---
.../java/de/juplo/demo/ExampleService.java | 11 +-
src/main/resources/templates/view.html | 2 +-
.../demo/ExceptionHandlingApplicationIT.java | 185 ------------------
.../ExceptionHandlingApplicationTests.java | 128 +-----------
6 files changed, 6 insertions(+), 369 deletions(-)
delete mode 100644 src/test/java/de/juplo/demo/ExceptionHandlingApplicationIT.java
diff --git a/pom.xml b/pom.xml
index 546ac9f..09a4f3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,17 +51,6 @@
-
- org.jsoup
- jsoup
- 1.13.1
- test
-
-
- org.springframework.boot
- spring-boot-starter-webflux
- test
-
@@ -70,10 +59,6 @@
org.springframework.boot
spring-boot-maven-plugin
-
- org.apache.maven.plugins
- maven-failsafe-plugin
-
diff --git a/src/main/java/de/juplo/demo/ExampleController.java b/src/main/java/de/juplo/demo/ExampleController.java
index a6efeb2..6c49f6b 100644
--- a/src/main/java/de/juplo/demo/ExampleController.java
+++ b/src/main/java/de/juplo/demo/ExampleController.java
@@ -6,11 +6,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
-import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
-import org.thymeleaf.exceptions.TemplateProcessingException;
-
-import java.util.Optional;
@Controller
public class ExampleController
@@ -34,9 +30,7 @@ public class ExampleController
Model model
)
{
- Optional outcome =
- answer == null ? null : service.checkAnswer(answer);
-
+ Boolean outcome = answer == null ? null : service.checkAnswer(answer);
model.addAttribute("answer", answer);
model.addAttribute("outcome", outcome);
return "view";
@@ -51,31 +45,5 @@ public class ExampleController
mav.addObject("exception", e);
return mav;
}
-
- /**
- * This {@link ExceptionHandler @ExceptionHander} is never triggered,
- * because the exception is not thrown inside the controller:
- * It is functionless!
- *
- * The exception is thrown by Thymeleaf, which is called by the
- * {@link DispatcherServlet} after the controller has finished its
- * work during the rendering of the outcome.
- *
- *
- * {@link ExceptionHandler @ExceptionHander's} are not able, to catch
- * and resolve exceptions, which are thrown outside of the scope of the
- * controller.
- * This is also true for {@link ControllerAdvice}.
- *
- */
- @ResponseStatus(HttpStatus.BAD_REQUEST)
- @ExceptionHandler(TemplateProcessingException.class)
- public ModelAndView templateInputException(TemplateProcessingException e)
- {
- LOG.error("{}: {}", HttpStatus.BAD_REQUEST, e.getMessage());
- ModelAndView mav = new ModelAndView("400");
- mav.addObject("exception", e);
- return mav;
- }
}
diff --git a/src/main/java/de/juplo/demo/ExampleService.java b/src/main/java/de/juplo/demo/ExampleService.java
index 45f2ca1..a638ea3 100644
--- a/src/main/java/de/juplo/demo/ExampleService.java
+++ b/src/main/java/de/juplo/demo/ExampleService.java
@@ -2,25 +2,20 @@ package de.juplo.demo;
import org.springframework.stereotype.Component;
-import java.util.Optional;
-
@Component
public class ExampleService
{
- public Optional checkAnswer(int answer)
+ public Boolean checkAnswer(int answer)
{
- if (answer < 0)
- return Optional.empty();
-
if (answer == 42)
{
- return Optional.of(true);
+ return true;
}
else
{
if (answer % 7 == 0)
throw new IllegalArgumentException(answer + " is devidable by 7!");
- return Optional.of(false);
+ return false;
}
}
}
diff --git a/src/main/resources/templates/view.html b/src/main/resources/templates/view.html
index e632398..c862d36 100644
--- a/src/main/resources/templates/view.html
+++ b/src/main/resources/templates/view.html
@@ -13,7 +13,7 @@
- Presented answer: ANSWER
- - Outcome: OUTCOME
+ - Outcome: OUTCOME
diff --git a/src/test/java/de/juplo/demo/ExceptionHandlingApplicationIT.java b/src/test/java/de/juplo/demo/ExceptionHandlingApplicationIT.java
deleted file mode 100644
index d964e6c..0000000
--- a/src/test/java/de/juplo/demo/ExceptionHandlingApplicationIT.java
+++ /dev/null
@@ -1,185 +0,0 @@
-package de.juplo.demo;
-
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.test.web.reactive.server.WebTestClient;
-
-import java.util.Optional;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.*;
-
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-@AutoConfigureWebTestClient
-class ExceptionHandlingApplicationIT {
- private final static Logger LOG =
- LoggerFactory.getLogger(ExceptionHandlingApplicationIT.class);
-
- @MockBean
- ExampleService service;
-
- @Autowired
- WebTestClient client;
-
-
- @Test
- void contextLoads() throws Exception {
- }
-
- @Test
- void test200ForNoAnswer() throws Exception {
- client
- .get()
- .uri("/")
- .accept(MediaType.TEXT_HTML)
- .exchange()
- .expectStatus().isOk()
- .expectBody(String.class).value(content -> {
- Document doc = Jsoup.parse(content);
- assertThat(doc.select("ul > li")).isEmpty();
- });
-
- verify(service, times(0)).checkAnswer(anyInt());
- }
-
- @Test
- void test200ForEmptyAnswer() throws Exception {
- client
- .get()
- .uri("/?answer= ")
- .accept(MediaType.TEXT_HTML)
- .exchange()
- .expectStatus().isOk()
- .expectBody(String.class).value(content -> {
- Document doc = Jsoup.parse(content);
- assertThat(doc.select("ul > li")).isEmpty();
- });
-
- verify(service, times(0)).checkAnswer(anyInt());
- }
-
- @Test
- void test200ForAnswerThatContainsOnlyWhitespace() throws Exception {
- client
- .get()
- .uri("/?answer=")
- .accept(MediaType.TEXT_HTML)
- .exchange()
- .expectStatus().isOk()
- .expectBody(String.class).value(content -> {
- Document doc = Jsoup.parse(content);
- assertThat(doc.select("ul > li")).isEmpty();
- });
-
- verify(service, times(0)).checkAnswer(anyInt());
- }
-
- @Test
- void test200ForWrongAnswer() throws Exception {
- when(service.checkAnswer(anyInt())).thenReturn(Optional.of(false));
-
- client
- .get()
- .uri("/?answer=1234")
- .accept(MediaType.TEXT_HTML)
- .exchange()
- .expectStatus().isOk()
- .expectBody(String.class).value(content -> {
- Document doc = Jsoup.parse(content);
- assertThat(doc.selectFirst("ul > li:nth-child(2) > strong").text()).isEqualTo("false");
- });
-
- verify(service, times(1)).checkAnswer(anyInt());
- }
-
- @Test
- void test200ForCorrectAnswer() throws Exception {
- when(service.checkAnswer(anyInt())).thenReturn(Optional.of(true));
-
- client
- .get()
- .uri("/?answer=1234")
- .accept(MediaType.TEXT_HTML)
- .exchange()
- .expectStatus().isOk()
- .expectBody(String.class).value(content -> {
- Document doc = Jsoup.parse(content);
- assertThat(doc.selectFirst("ul > li:nth-child(2) > strong").text()).isEqualTo("true");
- });
-
- verify(service, times(1)).checkAnswer(anyInt());
- }
-
- @Test
- void testExceptionForNegativeAnswer() throws Exception {
- when(service.checkAnswer(anyInt())).thenReturn(Optional.empty());
-
- client
- .get()
- .uri("/?answer=1234")
- .accept(MediaType.TEXT_HTML)
- .exchange()
- .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR)
- .expectBody(String.class).value(content -> {
- Document doc = Jsoup.parse(content);
- assertThat(doc.selectFirst("title").text())
- .isEqualTo("500: Internal Server Error");
- assertThat(doc.selectFirst("div > p > strong").text())
- .isEqualTo("Catched exception: java.lang.IllegalArgumentException: FOO!");
- });
-
- verify(service, times(1)).checkAnswer(anyInt());
- }
-
- @Test
- void test400ForStringInput() throws Exception {
- when(service.checkAnswer(anyInt())).thenReturn(Optional.empty());
-
- client
- .get()
- .uri("/?answer=bar")
- .accept(MediaType.TEXT_HTML)
- .exchange()
- .expectStatus().isBadRequest()
- .expectBody(String.class).value(content -> {
- Document doc = Jsoup.parse(content);
- assertThat(doc.selectFirst("title").text())
- .isEqualTo("400: NumberFormatException");
- assertThat(doc.selectFirst("div > p > strong").text())
- .isEqualTo("Catched exception: java.lang.NumberFormatException: For input string: \"bar\"");
- });
-
- verify(service, times(0)).checkAnswer(anyInt());
- }
-
- @Test
- void test400ForExceptionInBusinessLogic() throws Exception {
- when(service.checkAnswer(anyInt())).thenThrow(new IllegalArgumentException("FOO!"));
-
- client
- .get()
- .uri("/?answer=1234")
- .accept(MediaType.TEXT_HTML)
- .exchange()
- .expectStatus().isBadRequest()
- .expectBody(String.class).value(content -> {
- Document doc = Jsoup.parse(content);
- assertThat(doc.selectFirst("title").text())
- .isEqualTo("400: IllegalArgumentException");
- assertThat(doc.selectFirst("div > p > strong").text())
- .isEqualTo("Catched exception: java.lang.IllegalArgumentException: FOO!");
- });
-
- verify(service, times(1)).checkAnswer(anyInt());
- }
-}
diff --git a/src/test/java/de/juplo/demo/ExceptionHandlingApplicationTests.java b/src/test/java/de/juplo/demo/ExceptionHandlingApplicationTests.java
index 62f3a13..77eb7f6 100644
--- a/src/test/java/de/juplo/demo/ExceptionHandlingApplicationTests.java
+++ b/src/test/java/de/juplo/demo/ExceptionHandlingApplicationTests.java
@@ -1,30 +1,20 @@
package de.juplo.demo;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.web.util.NestedServletException;
import java.net.URI;
-import java.util.Optional;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.*;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(ExampleController.class)
class ExceptionHandlingApplicationTests {
- private final static Logger LOG =
- LoggerFactory.getLogger(ExceptionHandlingApplicationTests.class);
@MockBean
ExampleService service;
@@ -33,129 +23,13 @@ class ExceptionHandlingApplicationTests {
MockMvc mvc;
- @Test
- void contextLoads() throws Exception {
- }
-
- @Test
- void test200ForNoAnswer() throws Exception {
- mvc
- .perform(get(URI.create("http://FOO/")))
- .andExpect(status().isOk())
- .andDo((result) -> {
- String content = result.getResponse().getContentAsString();
- Document doc = Jsoup.parse(content);
- assertThat(doc.select("ul > li")).isEmpty();
- });
-
- verify(service, times(0)).checkAnswer(anyInt());
- }
-
- @Test
- void test200ForEmptyAnswer() throws Exception {
- mvc
- .perform(get(URI.create("http://FOO/?answer=")))
- .andExpect(status().isOk())
- .andDo((result) -> {
- String content = result.getResponse().getContentAsString();
- Document doc = Jsoup.parse(content);
- assertThat(doc.select("ul > li")).isEmpty();
- });
-
- verify(service, times(0)).checkAnswer(anyInt());
- }
-
- @Test
- void test200ForAnswerThatContainsOnlyWhitespace() throws Exception {
- mvc
- .perform(get(URI.create("http://FOO/?answer=%20")))
- .andExpect(status().isOk())
- .andDo((result) -> {
- String content = result.getResponse().getContentAsString();
- Document doc = Jsoup.parse(content);
- assertThat(doc.select("ul > li")).isEmpty();
- });
-
- verify(service, times(0)).checkAnswer(anyInt());
- }
-
- @Test
- void test200ForWrongAnswer() throws Exception {
- when(service.checkAnswer(anyInt())).thenReturn(Optional.of(false));
-
- mvc
- .perform(get(URI.create("http://FOO/?answer=1234")))
- .andExpect(status().isOk())
- .andDo((result) -> {
- String content = result.getResponse().getContentAsString();
- Document doc = Jsoup.parse(content);
- assertThat(doc.selectFirst("ul > li:nth-child(2) > strong").text()).isEqualTo("false");
- });
-
- verify(service, times(1)).checkAnswer(anyInt());
- }
-
- @Test
- void test200ForCorrectAnswer() throws Exception {
- when(service.checkAnswer(anyInt())).thenReturn(Optional.of(true));
-
- mvc
- .perform(get(URI.create("http://FOO/?answer=1234")))
- .andExpect(status().isOk())
- .andDo((result) -> {
- String content = result.getResponse().getContentAsString();
- Document doc = Jsoup.parse(content);
- assertThat(doc.selectFirst("ul > li:nth-child(2) > strong").text()).isEqualTo("true");
- });
-
- verify(service, times(1)).checkAnswer(anyInt());
- }
-
- @Test
- void testExceptionForNegativeAnswer() throws Exception {
- when(service.checkAnswer(anyInt())).thenReturn(Optional.empty());
-
- // The corrected version of the test catches the wrapper NestedServletException
- // for exceptions, that are handled in the DispatcherServlet in a fully setup
- // and, hence, cannot be handled in a @WebMvcTest, that mocks this part of the stack.
- assertThrows(
- NestedServletException.class,
- () -> mvc.perform(get(URI.create("http://FOO/?answer=1234"))));
-
- verify(service, times(1)).checkAnswer(anyInt());
- }
-
- @Test
- void test400ForStringInput() throws Exception {
- when(service.checkAnswer(anyInt())).thenReturn(Optional.empty());
-
- // The expected behaviour of the following test is, that the WhiteLabel Error Page
- // is rendered, for the unallowed string-value.
- // Instead, an almost empty page is rendered for the error.
- mvc
- .perform(get(URI.create("http://FOO/?answer=bar")))
- .andExpect(status().isBadRequest());
- // Specifying exceptations for the rendered output is not useful,
- // because MockMvc does not render the White Label ErrorPage
-
- verify(service, times(0)).checkAnswer(anyInt());
- }
-
@Test
void test400ForExceptionInBusinessLogic() throws Exception {
when(service.checkAnswer(anyInt())).thenThrow(new IllegalArgumentException("FOO!"));
mvc
.perform(get(URI.create("http://FOO/?answer=1234")))
- .andExpect(status().isBadRequest())
- .andDo((result) -> {
- String content = result.getResponse().getContentAsString();
- Document doc = Jsoup.parse(content);
- assertThat(doc.selectFirst("title").text())
- .isEqualTo("400: IllegalArgumentException");
- assertThat(doc.selectFirst("div > p > strong").text())
- .isEqualTo("Catched exception: java.lang.IllegalArgumentException: FOO!");
- });
+ .andExpect(status().isBadRequest());
verify(service, times(1)).checkAnswer(anyInt());
}
--
2.20.1