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