10 스프링 MVC
p131 - build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.3'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'spring'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
p132(왼쪽이미지) - src/main/spring/mvc/domain/User
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long userIdx;
private String userId;
private String userName;
private String rank;
private LocalDate registrationDate;
private LocalDate updateDate;
}
p132(오른쪽이미지) - src/main/spring/mvc/domain/Book
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Book {
// 인덱스 번호
private Long bookIdx;
// 도서 이름
private String title;
// 출판사
private String publisher;
// 판매 가격
private int salePrice;
// 대여 비용
private int rentalPrice;
// 대여자
private String renter;
// 등록일자
private LocalDate registrationDate;
// 수정일자
private LocalDate updateDate;
}
p134, 139 - src/main/spring/mvc/controller/UserController
@Slf4j
@Controller
public class UserController {
@RequestMapping(value = "/user", method = RequestMethod.GET)
public ModelAndView register() {
ModelAndView modelAndView = new ModelAndView("register");
modelAndView.addObject("user", new User()); // 빈 User 객체를 뷰에 전달
return modelAndView;
}
@PostMapping("/submitUser")
public ModelAndView submitUser(HttpServletRequest request) {
// HttpServletRequest에서 폼 데이터를 직접 가져옴
String userId = request.getParameter("userId");
String userName = request.getParameter("userName");
String rank = request.getParameter("rank");
// User 객체를 수동으로 생성하고 값을 설정
User user = new User();
user.setUserId(userId);
user.setUserName(userName);
user.setRank(rank);
user.setRegistrationDate(LocalDate.now());
user.setUpdateDate(LocalDate.now());
// ModelAndView 객체 생성 및 데이터와 뷰 설정
ModelAndView modelAndView = new ModelAndView("userDetails"); // "userDetails"는 이동할 뷰 이름
modelAndView.addObject("user", user); // 모델에 user 객체 추가
return modelAndView; // ModelAndView 반환
}
@PostMapping("/submitUser")
public String submitUser(
@RequestParam("userId") String userId,
@RequestParam("userName") String userName,
@RequestParam("rank") String rank,
Model model
) {
// User 객체를 수동으로 생성하고 값을 설정
User user = new User();
user.setUserId(userId);
user.setUserName(userName);
user.setRank(rank);
user.setRegistrationDate(LocalDate.now());
user.setUpdateDate(LocalDate.now());
model.addAttribute("user", user); // 모델에 user 객체 추가
return "userDetails"; // ModelAndView 반환
}
}
p135(왼쪽이미지) - src/main/resources/templates/register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>사용자 등록</title>
</head>
<body>
<h2>사용자 등록 폼</h2>
<form action="/submitUser" method="post">
<label for="userId">아이디:</label>
<input type="text" id="userId" name="userId" required>
<br>
<label for="userName">이름:</label>
<input type="text" id="userName" name="userName" required>
<br>
<label for="rank">등급:</label>
<input type="text" id="rank" name="rank" required>
<br>
<button type="submit">등록</button>
</form>
</body>
</html>
p135(오른쪽이미지) - src/main/resources/templates/userDetails.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>사용자 정보</title>
</head>
<body>
<h2>등록된 사용자 정보</h2>
<p>아이디: <span th:text="${user.userId}"></span></p>
<p>이름: <span th:text="${user.userName}"></span></p>
<p>등급: <span th:text="${user.rank}"></span></p>
<p>등록 날짜: <span th:text="${user.registrationDate}"></span></p>
<p>업데이트 날짜: <span th:text="${user.updateDate}"></span></p>
</body>
</html>
p142, 144 - src/main/java/spring/mvc/controller/LoggerController
@Slf4j
@RestController
public class LoggerController {
@GetMapping("/log")
public String getLogTest() {
String name = "userName";
System.out.println("일반 System.out.println");
// Trace 로그 (가장 상세한 로그, 디버깅용)
log.trace("Trace 레벨 로그: 이 메시지는 매우 상세한 정보를 포함합니다.");
// Debug 로그 (디버깅 관련 정보)
log.debug("Debug 레벨 로그: 디버깅을 위한 로그입니다.");
// Info 로그 (일반적인 정보)
log.info("Info 레벨 로그: 애플리케이션의 일반적인 동작을 나타냅니다. name={}",name);
// Warn 로그 (경고 메시지, 오류까지는 아니지만 주의가 필요한 상황)
log.warn("Warn 레벨 로그: 경고입니다. 잠재적인 문제가 있을 수 있습니다.");
// Error 로그 (오류가 발생했을 때 사용)
log.error("Error 레벨 로그: 오류가 발생했습니다!");
return "로그 테스트 완료";
}
// private static final Logger logger = LoggerFactory.getLogger(LoggerController.class);
// @GetMapping("/log")
// public String getLogTest() {
// System.out.println("일반 System.out.println");
// // Trace 로그 (가장 상세한 로그, 디버깅용)
// logger.trace("Trace 레벨 로그: 이 메시지는 매우 상세한 정보를 포함합니다.");
//
// // Debug 로그 (디버깅 관련 정보)
// logger.debug("Debug 레벨 로그: 디버깅을 위한 로그입니다.");
//
// // Info 로그 (일반적인 정보)
// logger.info("Info 레벨 로그: 애플리케이션의 일반적인 동작을 나타냅니다.");
//
// // Warn 로그 (경고 메시지, 오류까지는 아니지만 주의가 필요한 상황)
// logger.warn("Warn 레벨 로그: 경고입니다. 잠재적인 문제가 있을 수 있습니다.");
//
// // Error 로그 (오류가 발생했을 때 사용)
// logger.error("Error 레벨 로그: 오류가 발생했습니다!");
//
// return "로그 테스트 완료";
// }
}
p148,149,150,151,152,153,154 - src/main/java/spring/mvc/controller/VariableTestController
@Slf4j
@RestController
public class VariableTestController {
@GetMapping("/path/{userId}")
public boolean pathTest(
@PathVariable String userId
) {
log.info("path variable = {}", userId);
return true;
}
@GetMapping("/path2")
public boolean paramTest(
@RequestParam(name = "name", defaultValue = "test", required = false) String name
) {
log.info("defaultValue Test = {}", name);
return true;
}
@GetMapping("/path3")
public boolean paramMapTest(
@RequestParam Map<String, Object> paramMap
) {
String userName = (String) paramMap.get("userName");
String userId = (String) paramMap.get("userId");
log.info("userName = {}, userId = {}, age = {}", userName, userId);
return true;
}
@GetMapping("/path4")
public boolean modelAttributeTest(
@ModelAttribute User user
) {
log.info("userId = {}, userName = {}, userIdx = {}", user.getUserId(), user.getUserName(), user.getUserIdx());
return true;
}
@PostMapping("/path5")
public boolean requestBody(
@RequestBody String message
) {
log.info("message body = {}", message);
return true;
}
@GetMapping(value = "/header", headers = "Special=specialTest")
public boolean headerTest() {
log.info("Special Test");
return true;
}
@GetMapping(value = "/produces-consumes", consumes = "application/json", produces = "application/json")
public String consumesProducesTest() {
log.info("Consumes JSON and Produces JSON");
return "{\"message\":\"Success\"}";
}
}
p157, 158 - src/main/java/spring/mvc/repository/UserRepository
@Slf4j
@Repository
public class UserRepository {
// ConcurrentHashMap을 이용한 데이터 저장소
private final ConcurrentMap<Long, User> usersMap = new ConcurrentHashMap<>();
private static Long userIndex = 1L;
// 기본 유저 등록
public UserRepository() {
}
// CREATE: User 객체 저장
public User save(User user) {
user.setUserIdx(++userIndex);
usersMap.put(user.getUserIdx(), user);
return user;
}
// READ: userIdx로 검색
public Optional<User> findByUserIdx(Long userIdx) {
return Optional.ofNullable(usersMap.get(userIdx));
}
// READ: user_id로 검색
public Optional<User> findByUserId(String user_id) {
return usersMap.values().stream()
.filter(user -> user.getUserId().equals(user_id))
.findAny();
}
// READ: user_name으로 검색
public List<User> findByUserName(String user_name) {
return usersMap.values().stream()
.filter(user -> user.getUserName().equals(user_name))
.collect(Collectors.toList());
}
// READ: 모든 데이터 찾기
public List<User> findAll() {
return new ArrayList<>(usersMap.values());
}
// UPDATE: 특정 User 업데이트
public Optional<User> update(Long userIdx, User updatedUser) {
if (usersMap.containsKey(userIdx)) {
updatedUser.setUserIdx(userIdx); // 기존 userIdx 유지
usersMap.put(userIdx, updatedUser);
return Optional.of(updatedUser);
}
return Optional.empty();
}
// DELETE: 특정 User 삭제
public boolean delete(Long userIdx) {
return usersMap.remove(userIdx) != null;
}
}
p159~163 - src/test/java/spring/mvc/repository/UserRepositoryTest
class UserRepositoryTest {
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository = new UserRepository();
}
@Test
void save() {
// given
User user = new User(null, "user1", "User One", "A", null, null);
// when
User savedUser = userRepository.save(user);
// then
assertNotNull(savedUser.getUserIdx());
Assertions.assertThat(userRepository.findByUserIdx(savedUser.getUserIdx())).isPresent();
assertEquals("user1", savedUser.getUserId());
}
@Test
void findByUserIdx() {
// given
User user = new User(null, "user2", "User Two", "B", null, null);
User savedUser = userRepository.save(user);
// when
Optional<User> foundUser = userRepository.findByUserIdx(savedUser.getUserIdx());
// then
assertTrue(foundUser.isPresent());
assertEquals(savedUser.getUserIdx(), foundUser.get().getUserIdx());
assertEquals("User Two", foundUser.get().getUserName());
}
@Test
void findByUserId() {
// given
User user = new User(null, "user3", "User Three", "A", null, null);
userRepository.save(user);
// when
Optional<User> foundUser = userRepository.findByUserId("user3");
// then
assertTrue(foundUser.isPresent());
assertEquals("User Three", foundUser.get().getUserName());
}
@Test
void findByUserName() {
// given
User user1 = new User(null, "user4", "Same Name", "A", null, null);
User user2 = new User(null, "user5", "Same Name", "B", null, null);
userRepository.save(user1);
userRepository.save(user2);
// when
List<User> foundUsers = userRepository.findByUserName("Same Name");
// then
assertEquals(2, foundUsers.size());
assertTrue(foundUsers.stream().allMatch(user -> user.getUserName().equals("Same Name")));
}
@Test
void findAll() {
// given
User user1 = new User(null, "user6", "User Six", "A", null, null);
User user2 = new User(null, "user7", "User Seven", "B", null, null);
userRepository.save(user1);
userRepository.save(user2);
// when
List<User> allUsers = userRepository.findAll();
// then
assertEquals(2, allUsers.size());
}
@Test
void update() {
// given
User user = new User(null, "user8", "User Eight", "A", null, null);
User savedUser = userRepository.save(user);
// when
savedUser.setUserName("Updated User Eight");
savedUser.setRank("B");
Optional<User> updatedUser = userRepository.update(savedUser.getUserIdx(), savedUser);
// then
assertTrue(updatedUser.isPresent());
assertEquals("Updated User Eight", updatedUser.get().getUserName());
assertEquals("B", updatedUser.get().getRank());
}
@Test
void delete() {
// given
User user = new User(null, "user9", "User Nine", "A", null, null);
User savedUser = userRepository.save(user);
// when
boolean isDeleted = userRepository.delete(savedUser.getUserIdx());
// then
assertTrue(isDeleted);
assertFalse(userRepository.findByUserIdx(savedUser.getUserIdx()).isPresent());
}
}
p167,168 - src/main/java/spring/mvc/service/UserService
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// CREATE: 새로운 사용자 생성
public User createUser(User user) {
return userRepository.save(user);
}
// READ: userIdx로 사용자 검색
public Optional<User> getUserById(Long userIdx) {
return userRepository.findByUserIdx(userIdx);
}
// READ: userId로 사용자 검색
public Optional<User> getUserByUserId(String userId) {
return userRepository.findByUserId(userId);
}
// READ: userName으로 사용자 검색
public List<User> getUsersByUserName(String userName) {
return userRepository.findByUserName(userName);
}
// READ: 모든 사용자 조회
public List<User> getAllUsers() {
return userRepository.findAll();
}
// UPDATE: 기존 사용자 정보 업데이트
public Optional<User> updateUser(Long userIdx, User updatedUser) {
return userRepository.update(userIdx, updatedUser);
}
// DELETE: 사용자 삭제
public boolean deleteUser(Long userIdx) {
return userRepository.delete(userIdx);
}
}
p169~173 - src/main/java/spring/mvc/controller/UserRestController
@Slf4j
@RestController
@RequestMapping("/user")
public class UserRestController {
private final ObjectMapper objectMapper = new ObjectMapper();
private final UserService userService;
@Autowired
public UserRestController(UserService userService) {
this.userService = userService;
}
// 에러 메시지를 JSON 형태로 반환하는 메서드
private String getErrorJson(String message) {
Map<String, String> errorResponse = new HashMap<>();
errorResponse.put("error", message);
try {
return objectMapper.writeValueAsString(errorResponse);
} catch (JsonProcessingException e) {
return "{\"error\": \"JSON 처리 중 오류가 발생했습니다.\"}";
}
}
/**
* 등록 (POST) - 새로운 사용자 생성
*/
@PostMapping("/")
public String registerUser(@RequestBody User newUser) {
try {
User createdUser = userService.createUser(newUser);
String returnJson = objectMapper.writeValueAsString(createdUser);
log.info("User created: {}", returnJson);
return returnJson;
} catch (JsonProcessingException e) {
log.error("JSON 처리 오류: {}", e.getMessage());
return getErrorJson("사용자 생성 중 오류 발생");
}
}
/**
* 조회 (GET) - 모든 사용자 목록 조회
*/
@GetMapping("/list")
public String listUsers() {
try {
List<User> users = userService.getAllUsers();
String returnJson = objectMapper.writeValueAsString(users);
log.info("Users list: {}", returnJson);
return returnJson;
} catch (JsonProcessingException e) {
log.error("JSON 처리 오류: {}", e.getMessage());
return getErrorJson("사용자 목록 조회 중 오류 발생");
}
}
/**
* 조회 (GET) - ID로 사용자 조회
*/
@GetMapping("/{userIdx}")
public String getUserById(@PathVariable Long userIdx) {
Optional<User> user = userService.getUserById(userIdx);
if (user.isPresent()) {
try {
String returnJson = objectMapper.writeValueAsString(user.get());
log.info("User found: {}", returnJson);
return returnJson;
} catch (JsonProcessingException e) {
log.error("JSON 처리 오류: {}", e.getMessage());
return getErrorJson("사용자 조회 중 오류 발생");
}
} else {
log.warn("User with ID {} not found", userIdx);
return getErrorJson("사용자를 찾을 수 없습니다.");
}
}
/**
* 수정 (PATCH) - ID로 사용자 정보 수정
*/
@PatchMapping("/{userIdx}")
public String updateUser(@PathVariable Long userIdx, @RequestBody User updatedUser) {
Optional<User> updated = userService.updateUser(userIdx, updatedUser);
if (updated.isPresent()) {
try {
String returnJson = objectMapper.writeValueAsString(updated.get());
log.info("User updated: {}", returnJson);
return returnJson;
} catch (JsonProcessingException e) {
log.error("JSON 처리 오류: {}", e.getMessage());
return getErrorJson("사용자 업데이트 중 오류 발생");
}
} else {
log.warn("User with ID {} not found", userIdx);
return getErrorJson("사용자를 찾을 수 없습니다.");
}
}
/**
* 삭제 (DELETE) - ID로 사용자 삭제
*/
@DeleteMapping("/{userIdx}")
public String deleteUser(@PathVariable Long userIdx) {
boolean isDeleted = userService.deleteUser(userIdx);
if (isDeleted) {
log.info("User with ID {} deleted successfully", userIdx);
return "{\"message\": \"사용자가 성공적으로 삭제되었습니다.\"}";
} else {
log.warn("User with ID {} not found", userIdx);
return getErrorJson("사용자를 찾을 수 없습니다.");
}
}
}
p174 - src/main/java/spring/mvc/domain/Book
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Book {
// 인덱스 번호
private Long bookIdx;
// 도서 이름
@NotBlank
private String title;
// 출판사
@NotBlank
private String publisher;
// 판매 가격
@NotNull
@Min(0)
private int salePrice;
// 대여 비용
private int rentalPrice;
// 대여자
private String renter;
// 등록일자
private LocalDate registrationDate;
// 수정일자
private LocalDate updateDate;
}
p175 - src/main/java/spring/mvc/repository/BookRepository
@Repository
public class BookRepository {
private final ConcurrentMap<Long, Book> bookMap = new ConcurrentHashMap<>();
private static Long bookIndex = 0L;
// private 생성자: 외부에서 인스턴스 생성 불가
private BookRepository() {}
// CREATE: Book 객체 저장
public Book save(Book book) {
book.setBookIdx(++bookIndex);
bookMap.put(book.getBookIdx(), book);
return book;
}
// READ: ID로 책 검색
public Optional<Book> findById(Long bookIdx) {
return Optional.ofNullable(bookMap.get(bookIdx));
}
// READ: 모든 책 리스트 반환
public List<Book> findAll() {
return new ArrayList<>(bookMap.values());
}
// UPDATE: 기존 책 정보 수정
public Optional<Book> update(Long bookIdx, Book updatedBook) {
if (bookMap.containsKey(bookIdx)) {
updatedBook.setBookIdx(bookIdx); // 기존 ID 유지
bookMap.put(bookIdx, updatedBook);
return Optional.of(updatedBook);
}
return Optional.empty();
}
// DELETE: ID로 책 삭제
public boolean delete(Long bookIdx) {
return bookMap.remove(bookIdx) != null;
}
}
p177, 178 - src/main/java/spring/mvc/service/BookService
@Service
public class BookService {
private final BookRepository bookRepository;
private final UserRepository userRepository;
@Autowired
public BookService(BookRepository bookRepository, UserRepository userRepository) {
this.bookRepository = bookRepository;
this.userRepository = userRepository;
}
// CREATE: 새로운 책 저장
public Book createBook(Book book) {
return bookRepository.save(book);
}
// READ: ID로 책 검색
public Optional<Book> getBookById(Long bookIdx) {
return bookRepository.findById(bookIdx);
}
// READ: 모든 책 리스트 검색
public List<Book> getAllBooks() {
return bookRepository.findAll();
}
// UPDATE: 책 정보 수정
public Optional<Book> updateBook(Long bookIdx, Book updatedBook) {
return bookRepository.update(bookIdx, updatedBook);
}
// DELETE: 책 삭제
public boolean deleteBook(Long bookIdx) {
return bookRepository.delete(bookIdx);
}
// BOOKING: userIdx로 유저 검색해서 renter에 유저 이름 추가
public Optional<Book> rentBook(Long bookIdx, Long userIdx) {
// 책 검색
Optional<Book> bookOpt = bookRepository.findById(bookIdx);
if (bookOpt.isPresent()) {
Book book = bookOpt.get();
// 이미 대여 중인 경우 에러 처리
if (book.getRenter() != null && !book.getRenter().isEmpty()) {
throw new IllegalStateException("이 책은 이미 대여 중입니다.");
}
// 유저 검색
Optional<User> userOpt = userRepository.findByUserIdx(userIdx);
if (userOpt.isPresent()) {
User user = userOpt.get();
// 책의 대여자 정보를 유저 이름으로 설정
book.setRenter(user.getUserName());
// 업데이트된 책 정보 저장
return bookRepository.update(bookIdx, book);
} else {
throw new IllegalArgumentException("유효하지 않은 사용자입니다.");
}
}
return Optional.empty();
}
// RETURN: 책 반납
public Optional<Book> returnBook(Long bookIdx, Long userIdx) {
// 책 검색
Optional<Book> bookOpt = bookRepository.findById(bookIdx);
if (bookOpt.isPresent()) {
Book book = bookOpt.get();
// 책이 대여 중인지 확인
if (book.getRenter() == null || book.getRenter().isEmpty()) {
throw new IllegalStateException("이 책은 대여 중이 아닙니다.");
}
// 유저 검색
Optional<User> userOpt = userRepository.findByUserIdx(userIdx);
if (userOpt.isPresent()) {
User user = userOpt.get();
// 반납하는 유저가 대여자와 일치하는지 확인
if (!book.getRenter().equals(user.getUserName())) {
throw new IllegalArgumentException("반납 요청자가 대여자와 일치하지 않습니다.");
}
// 책의 대여자 정보를 삭제 (반납 처리)
book.setRenter(null);
// 업데이트된 책 정보 저장
return bookRepository.update(bookIdx, book);
} else {
throw new IllegalArgumentException("유효하지 않은 사용자입니다.");
}
}
return Optional.empty();
}
}
p179 ~186- src/test/java/spring/mvc/service/BookServiceTest
class BookServiceTest {
@Mock
private BookRepository bookRepository;
@Mock
private UserRepository userRepository;
@InjectMocks
private BookService bookService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void createBook() {
Book book = new Book();
book.setTitle("Test Book");
when(bookRepository.save(book)).thenReturn(book);
Book createdBook = bookService.createBook(book);
assertNotNull(createdBook);
assertEquals("Test Book", createdBook.getTitle());
verify(bookRepository, times(1)).save(book);
}
@Test
void getBookById() {
Long bookIdx = 1L;
Book book = new Book();
book.setBookIdx(bookIdx);
book.setTitle("Test Book");
when(bookRepository.findById(bookIdx)).thenReturn(Optional.of(book));
Optional<Book> foundBook = bookService.getBookById(bookIdx);
assertTrue(foundBook.isPresent());
assertEquals(bookIdx, foundBook.get().getBookIdx());
verify(bookRepository, times(1)).findById(bookIdx);
}
@Test
void getAllBooks() {
Book book1 = new Book();
book1.setTitle("Book 1");
Book book2 = new Book();
book2.setTitle("Book 2");
when(bookRepository.findAll()).thenReturn(Arrays.asList(book1, book2));
List<Book> books = bookService.getAllBooks();
assertEquals(2, books.size());
verify(bookRepository, times(1)).findAll();
}
@Test
void updateBook() {
Long bookIdx = 1L;
Book updatedBook = new Book();
updatedBook.setTitle("Updated Book");
when(bookRepository.update(bookIdx, updatedBook)).thenReturn(Optional.of(updatedBook));
Optional<Book> result = bookService.updateBook(bookIdx, updatedBook);
assertTrue(result.isPresent());
assertEquals("Updated Book", result.get().getTitle());
verify(bookRepository, times(1)).update(bookIdx, updatedBook);
}
@Test
void deleteBook() {
Long bookIdx = 1L;
when(bookRepository.delete(bookIdx)).thenReturn(true);
boolean result = bookService.deleteBook(bookIdx);
assertTrue(result);
verify(bookRepository, times(1)).delete(bookIdx);
}
@Test
void rentBook() {
Long bookIdx = 1L;
Long userIdx = 1L;
Book book = new Book();
book.setBookIdx(bookIdx);
User user = new User();
user.setUserIdx(userIdx);
user.setUserName("Test User");
// 레포지토리 동작 모킹
when(bookRepository.findById(bookIdx)).thenReturn(Optional.of(book));
when(userRepository.findByUserIdx(userIdx)).thenReturn(Optional.of(user));
when(bookRepository.update(bookIdx, book)).thenReturn(Optional.of(book));
// rentBook 메서드 호출
Optional<Book> rentedBook = bookService.rentBook(bookIdx, userIdx);
// 대여된 책이 존재하는지 검증
assertTrue(rentedBook.isPresent());
assertEquals("Test User", rentedBook.get().getRenter());
// 레포지토리 메서드가 올바르게 호출되었는지 검증
verify(bookRepository, times(1)).findById(bookIdx);
verify(userRepository, times(1)).findByUserIdx(userIdx);
verify(bookRepository, times(1)).update(bookIdx, book);
}
@Test
void rentBookAlreadyRented() {
Long bookIdx = 1L;
Long userIdx = 1L;
Book book = new Book();
book.setBookIdx(bookIdx);
book.setRenter("Already Rented");
// 레포지토리 동작 모킹
when(bookRepository.findById(bookIdx)).thenReturn(Optional.of(book));
// 예외가 발생하는지 검증
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
bookService.rentBook(bookIdx, userIdx);
});
// 예외 메시지가 올바른지 검증
assertEquals("이 책은 이미 대여 중입니다.", exception.getMessage());
// 책이 검색되었는지 확인, 다른 작업은 수행되지 않음
verify(bookRepository, times(1)).findById(bookIdx);
verify(userRepository, times(0)).findByUserIdx(anyLong()); // 유저 확인은 호출되지 않아야 함
verify(bookRepository, times(0)).update(anyLong(), any(Book.class));
}
@Test
void rentBookUserNotFound() {
Long bookIdx = 1L;
Long userIdx = 1L;
Book book = new Book();
book.setBookIdx(bookIdx);
// 레포지토리 동작 모킹
when(bookRepository.findById(bookIdx)).thenReturn(Optional.of(book));
when(userRepository.findByUserIdx(userIdx)).thenReturn(Optional.empty());
// 예외가 발생하는지 검증
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
bookService.rentBook(bookIdx, userIdx);
});
// 예외 메시지가 올바른지 검증
assertEquals("유효하지 않은 사용자입니다.", exception.getMessage());
// 레포지토리 메서드가 올바르게 호출되었는지 검증
verify(bookRepository, times(1)).findById(bookIdx);
verify(userRepository, times(1)).findByUserIdx(userIdx);
verify(bookRepository, times(0)).update(anyLong(), any(Book.class));
}
@Test
void rentBookBookNotFound() {
Long bookIdx = 1L;
Long userIdx = 1L;
// 레포지토리 동작 모킹
when(bookRepository.findById(bookIdx)).thenReturn(Optional.empty());
// rentBook 메서드 호출
Optional<Book> rentedBook = bookService.rentBook(bookIdx, userIdx);
// 대여된 책이 없음을 검증
assertFalse(rentedBook.isPresent());
// 레포지토리 메서드가 올바르게 호출되었는지 검증
verify(bookRepository, times(1)).findById(bookIdx);
verify(userRepository, times(0)).findByUserIdx(anyLong());
verify(bookRepository, times(0)).update(anyLong(), any(Book.class));
}
@Test
void returnBook() {
Long bookIdx = 1L;
Long userIdx = 1L;
Book book = new Book();
book.setBookIdx(bookIdx);
book.setRenter("Test User");
User user = new User();
user.setUserIdx(userIdx);
user.setUserName("Test User");
when(bookRepository.findById(bookIdx)).thenReturn(Optional.of(book));
when(userRepository.findByUserIdx(userIdx)).thenReturn(Optional.of(user));
when(bookRepository.update(bookIdx, book)).thenReturn(Optional.of(book));
Optional<Book> returnedBook = bookService.returnBook(bookIdx, userIdx);
assertTrue(returnedBook.isPresent());
assertNull(returnedBook.get().getRenter());
verify(bookRepository, times(1)).findById(bookIdx);
verify(userRepository, times(1)).findByUserIdx(userIdx);
verify(bookRepository, times(1)).update(bookIdx, book);
}
@Test
void returnBookUserNotFound() {
Long bookIdx = 1L;
Long userIdx = 1L;
Book book = new Book();
book.setBookIdx(bookIdx);
book.setRenter("Test User");
// 책은 존재하지만 유저는 존재하지 않는 경우를 모킹
when(bookRepository.findById(bookIdx)).thenReturn(Optional.of(book));
when(userRepository.findByUserIdx(userIdx)).thenReturn(Optional.empty());
// 예외가 발생하는지 검증
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
bookService.returnBook(bookIdx, userIdx);
});
// 예외 메시지가 올바른지 검증
assertEquals("유효하지 않은 사용자입니다.", exception.getMessage());
// 레포지토리 메서드 호출 검증
verify(bookRepository, times(1)).findById(bookIdx);
verify(userRepository, times(1)).findByUserIdx(userIdx);
verify(bookRepository, times(0)).update(anyLong(), any(Book.class));
}
@Test
void returnBookWrongUser() {
Long bookIdx = 1L;
Long userIdx = 1L;
Book book = new Book();
book.setBookIdx(bookIdx);
book.setRenter("Test User");
User user = new User();
user.setUserIdx(userIdx);
user.setUserName("Another User");
// 책과 유저를 모킹, 유저가 대여자와 다름
when(bookRepository.findById(bookIdx)).thenReturn(Optional.of(book));
when(userRepository.findByUserIdx(userIdx)).thenReturn(Optional.of(user));
// 예외가 발생하는지 검증
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
bookService.returnBook(bookIdx, userIdx);
});
// 예외 메시지가 올바른지 검증
assertEquals("반납 요청자가 대여자와 일치하지 않습니다.", exception.getMessage());
// 레포지토리 메서드 호출 검증
verify(bookRepository, times(1)).findById(bookIdx);
verify(userRepository, times(1)).findByUserIdx(userIdx);
verify(bookRepository, times(0)).update(anyLong(), any(Book.class));
}
@Test
void returnBookBookNotFound() {
Long bookIdx = 1L;
Long userIdx = 1L;
// 책이 존재하지 않는 경우를 모킹
when(bookRepository.findById(bookIdx)).thenReturn(Optional.empty());
// 메서드 호출
Optional<Book> returnedBook = bookService.returnBook(bookIdx, userIdx);
// 반환 값이 비어있는지 확인
assertFalse(returnedBook.isPresent());
// 레포지토리 메서드 호출 검증
verify(bookRepository, times(1)).findById(bookIdx);
verify(userRepository, times(0)).findByUserIdx(anyLong());
verify(bookRepository, times(0)).update(anyLong(), any(Book.class));
}
@Test
void returnBookNotRented() {
Long bookIdx = 1L;
Long userIdx = 1L;
Book book = new Book();
book.setBookIdx(bookIdx);
when(bookRepository.findById(bookIdx)).thenReturn(Optional.of(book));
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
bookService.returnBook(bookIdx, userIdx);
});
assertEquals("이 책은 대여 중이 아닙니다.", exception.getMessage());
verify(bookRepository, times(1)).findById(bookIdx);
}
}
p187 ~ 193 - src/main/java/spring/mvc/controller/BookController
@Slf4j
@RestController
@RequestMapping("/books")
public class BookController {
private final BookService bookService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
public BookController(BookService bookService) {
this.bookService = bookService;
}
// JSON 변환을 위한 헬퍼 메서드
private String convertToJson(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
log.error("JSON 변환 오류: {}", e.getMessage());
return "{\"error\": \"JSON 변환 중 오류가 발생했습니다.\"}";
}
}
// CREATE: 새로운 책 저장
@PostMapping
public String createBook(@RequestBody Book book) {
Book createdBook = bookService.createBook(book);
return convertToJson(createdBook);
}
// READ: ID로 책 검색
@GetMapping("/{bookIdx}")
public String getBookById(@PathVariable Long bookIdx) {
Optional<Book> book = bookService.getBookById(bookIdx);
if (book.isPresent()) {
return convertToJson(book.get());
} else {
return "{\"error\": \"책을 찾을 수 없습니다.\"}";
}
}
// READ: 모든 책 리스트 검색
@GetMapping
public String getAllBooks() {
List<Book> books = bookService.getAllBooks();
return convertToJson(books);
}
// UPDATE: 책 정보 수정
@PutMapping("/{bookIdx}")
public String updateBook(@PathVariable Long bookIdx, @RequestBody Book updatedBook) {
Optional<Book> book = bookService.updateBook(bookIdx, updatedBook);
if (book.isPresent()) {
return convertToJson(book.get());
} else {
return "{\"error\": \"책을 찾을 수 없습니다.\"}";
}
}
// DELETE: 책 삭제
@DeleteMapping("/{bookIdx}")
public String deleteBook(@PathVariable Long bookIdx) {
boolean deleted = bookService.deleteBook(bookIdx);
if (deleted) {
return "{\"message\": \"책이 성공적으로 삭제되었습니다.\"}";
} else {
return "{\"error\": \"책을 찾을 수 없습니다.\"}";
}
}
// BOOKING: 책 대여
@PostMapping("/{bookIdx}/rent/{userIdx}")
public String rentBook(@PathVariable Long bookIdx, @PathVariable Long userIdx) {
try {
Optional<Book> rentedBook = bookService.rentBook(bookIdx, userIdx);
if (rentedBook.isPresent()) {
return convertToJson(rentedBook.get());
} else {
return "{\"error\": \"책을 찾을 수 없습니다.\"}";
}
} catch (IllegalStateException | IllegalArgumentException e) {
log.error("책 대여 오류: {}", e.getMessage());
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
// RETURN: 책 반납
@PostMapping("/{bookIdx}/return/{userIdx}")
public String returnBook(@PathVariable Long bookIdx, @PathVariable Long userIdx) {
try {
Optional<Book> returnedBook = bookService.returnBook(bookIdx, userIdx);
if (returnedBook.isPresent()) {
return convertToJson(returnedBook.get());
} else {
return "{\"error\": \"책을 찾을 수 없습니다.\"}";
}
} catch (IllegalStateException | IllegalArgumentException e) {
log.error("책 반납 오류: {}", e.getMessage());
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
}
Last updated