BAEL-3225: Added Spring example for error handling best practices. (#7854)
This commit is contained in:
parent
56cae916aa
commit
b2908200f1
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user