BAEL-3225: Added Spring example for error handling best practices. (#7854)

This commit is contained in:
Justin Albano 2019-09-29 23:28:45 -04:00 committed by KevinGilmore
parent 56cae916aa
commit b2908200f1
8 changed files with 286 additions and 0 deletions

View File

@ -0,0 +1,25 @@
package com.baeldung.repository;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
import com.baeldung.web.dto.Book;
@Repository
public class BookRepository {
private List<Book> books = new ArrayList<>();
public Optional<Book> findById(long id) {
return books.stream()
.filter(book -> book.getId() == id)
.findFirst();
}
public void add(Book book) {
books.add(book);
}
}

View File

@ -0,0 +1,22 @@
package com.baeldung.web.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.baeldung.web.error.ApiErrorResponse;
import com.baeldung.web.error.BookNotFoundException;
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<ApiErrorResponse> handleApiException(
BookNotFoundException ex) {
ApiErrorResponse response =
new ApiErrorResponse("error-0001",
"No book found with ID " + ex.getId());
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
}

View File

@ -0,0 +1,25 @@
package com.baeldung.web.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.baeldung.repository.BookRepository;
import com.baeldung.web.dto.Book;
import com.baeldung.web.error.BookNotFoundException;
@RestController
@RequestMapping("/api/book")
public class BookController {
@Autowired
private BookRepository repository;
@GetMapping("/{id}")
public Book findById(@PathVariable long id) {
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
}
}

View File

@ -0,0 +1,32 @@
package com.baeldung.web.dto;
public class Book {
private long id;
private String title;
private String author;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}

View File

@ -0,0 +1,29 @@
package com.baeldung.web.error;
public class ApiErrorResponse {
private String error;
private String message;
public ApiErrorResponse(String error, String message) {
super();
this.error = error;
this.message = message;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,19 @@
package com.baeldung.web.error;
@SuppressWarnings("serial")
public class BookNotFoundException extends RuntimeException {
private long id;
public BookNotFoundException(long id) {
this.id = id;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}

View File

@ -0,0 +1,60 @@
package com.baeldung.repository;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import com.baeldung.web.dto.Book;
public class BookRepositoryUnitTest {
private BookRepository repository;
@Before
public void setUp() {
repository = new BookRepository();
}
@Test
public void givenNoBooks_WhenFindById_ThenReturnEmptyOptional() {
assertFalse(repository.findById(1).isPresent());
}
@Test
public void givenOneMatchingBook_WhenFindById_ThenReturnEmptyOptional() {
long id = 1;
Book expected = bookWithId(id);
repository.add(expected);
Optional<Book> found = repository.findById(id);
assertTrue(found.isPresent());
assertEquals(expected, found.get());
}
private static Book bookWithId(long id) {
Book book = new Book();
book.setId(id);
return book;
}
@Test
public void givenOneNonMatchingBook_WhenFindById_ThenReturnEmptyOptional() {
long id = 1;
Book expected = bookWithId(id);
repository.add(expected);
Optional<Book> found = repository.findById(id + 1);
assertFalse(found.isPresent());
}
}

View File

@ -0,0 +1,74 @@
package com.baeldung.web.controller;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
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.context.annotation.ComponentScan;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import com.baeldung.Application;
import com.baeldung.repository.BookRepository;
import com.baeldung.web.dto.Book;
import com.baeldung.web.error.ApiErrorResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
@ComponentScan(basePackageClasses = Application.class)
public class BookControllerIntegrationTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Autowired
private MockMvc mvc;
@MockBean
private BookRepository repository;
@Test
public void givenNoExistingBooks_WhenGetBookWithId1_ThenErrorReturned() throws Exception {
long id = 1;
doReturn(Optional.empty()).when(repository).findById(eq(id));
mvc.perform(get("/api/book/{id}", id))
.andExpect(status().isNotFound())
.andExpect(content().json(MAPPER.writeValueAsString(new ApiErrorResponse("error-0001", "No book found with ID " + id))));
}
@Test
public void givenMatchingBookExists_WhenGetBookWithId1_ThenBookReturned() throws Exception {
long id = 1;
Book book = book(id, "foo", "bar");
doReturn(Optional.of(book)).when(repository).findById(eq(id));
mvc.perform(get("/api/book/{id}", id))
.andExpect(status().isOk())
.andExpect(content().json(MAPPER.writeValueAsString(book)));
}
private static Book book(long id, String title, String author) {
Book book = new Book();
book.setId(id);
book.setTitle(title);
book.setAuthor(author);
return book;
}
}