diff --git a/spring-rest-simple/src/main/java/com/baeldung/repository/BookRepository.java b/spring-rest-simple/src/main/java/com/baeldung/repository/BookRepository.java new file mode 100644 index 0000000000..239d74b19c --- /dev/null +++ b/spring-rest-simple/src/main/java/com/baeldung/repository/BookRepository.java @@ -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 books = new ArrayList<>(); + + public Optional findById(long id) { + return books.stream() + .filter(book -> book.getId() == id) + .findFirst(); + } + + public void add(Book book) { + books.add(book); + } +} diff --git a/spring-rest-simple/src/main/java/com/baeldung/web/controller/ApiExceptionHandler.java b/spring-rest-simple/src/main/java/com/baeldung/web/controller/ApiExceptionHandler.java new file mode 100644 index 0000000000..e7f0ac3510 --- /dev/null +++ b/spring-rest-simple/src/main/java/com/baeldung/web/controller/ApiExceptionHandler.java @@ -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 handleApiException( + BookNotFoundException ex) { + ApiErrorResponse response = + new ApiErrorResponse("error-0001", + "No book found with ID " + ex.getId()); + return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); + } +} \ No newline at end of file diff --git a/spring-rest-simple/src/main/java/com/baeldung/web/controller/BookController.java b/spring-rest-simple/src/main/java/com/baeldung/web/controller/BookController.java new file mode 100644 index 0000000000..126b40517d --- /dev/null +++ b/spring-rest-simple/src/main/java/com/baeldung/web/controller/BookController.java @@ -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)); + } +} \ No newline at end of file diff --git a/spring-rest-simple/src/main/java/com/baeldung/web/dto/Book.java b/spring-rest-simple/src/main/java/com/baeldung/web/dto/Book.java new file mode 100644 index 0000000000..9ccd640319 --- /dev/null +++ b/spring-rest-simple/src/main/java/com/baeldung/web/dto/Book.java @@ -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; + } +} \ No newline at end of file diff --git a/spring-rest-simple/src/main/java/com/baeldung/web/error/ApiErrorResponse.java b/spring-rest-simple/src/main/java/com/baeldung/web/error/ApiErrorResponse.java new file mode 100644 index 0000000000..a67a7a3f2f --- /dev/null +++ b/spring-rest-simple/src/main/java/com/baeldung/web/error/ApiErrorResponse.java @@ -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; + } +} \ No newline at end of file diff --git a/spring-rest-simple/src/main/java/com/baeldung/web/error/BookNotFoundException.java b/spring-rest-simple/src/main/java/com/baeldung/web/error/BookNotFoundException.java new file mode 100644 index 0000000000..ad93ef7f20 --- /dev/null +++ b/spring-rest-simple/src/main/java/com/baeldung/web/error/BookNotFoundException.java @@ -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; + } +} \ No newline at end of file diff --git a/spring-rest-simple/src/test/java/com/baeldung/repository/BookRepositoryUnitTest.java b/spring-rest-simple/src/test/java/com/baeldung/repository/BookRepositoryUnitTest.java new file mode 100644 index 0000000000..dd64f302fe --- /dev/null +++ b/spring-rest-simple/src/test/java/com/baeldung/repository/BookRepositoryUnitTest.java @@ -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 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 found = repository.findById(id + 1); + + assertFalse(found.isPresent()); + } +} \ No newline at end of file diff --git a/spring-rest-simple/src/test/java/com/baeldung/web/controller/BookControllerIntegrationTest.java b/spring-rest-simple/src/test/java/com/baeldung/web/controller/BookControllerIntegrationTest.java new file mode 100644 index 0000000000..3bf5ce3c3d --- /dev/null +++ b/spring-rest-simple/src/test/java/com/baeldung/web/controller/BookControllerIntegrationTest.java @@ -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; + } +} \ No newline at end of file