</exclusion>
</exclusions>
</dependency>
- <dependency>
- <groupId>org.jsoup</groupId>
- <artifactId>jsoup</artifactId>
- <version>1.13.1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-webflux</artifactId>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-failsafe-plugin</artifactId>
- </plugin>
</plugins>
</build>
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
Model model
)
{
- Optional<Boolean> 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";
mav.addObject("exception", e);
return mav;
}
-
- /**
- * This {@link ExceptionHandler @ExceptionHander} is never triggered,
- * because the exception is not thrown inside the controller:
- * <strong>It is functionless!</strong>
- * <p>
- * 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.
- * </p>
- * <p>
- * {@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}.
- * </p>
- */
- @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;
- }
}
import org.springframework.stereotype.Component;
-import java.util.Optional;
-
@Component
public class ExampleService
{
- public Optional<Boolean> 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;
}
}
}
</p>
<ul th:case="*">
<li>Presented answer: <strong th:text="${answer}">ANSWER</strong></li>
- <li>Outcome: <strong th:text="${outcome.get()}">OUTCOME</strong></li>
+ <li>Outcome: <strong th:text="${outcome}">OUTCOME</strong></li>
</ul>
</div>
<div class="card-text">
+++ /dev/null
-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());
- }
-}
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;
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());
}