HTTP 상태 코드 : https://ko.wikipedia.org/wiki/HTTP_%EC%83%81%ED%83%9C_%EC%BD%94%EB%93%9C
플러그인 모음 사이트 : maven spring boot
plugins {
id 'org.springframework.boot' version '2.4.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
}
Reload
lombok 라이브러리 추가 후 리로드
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
annotationProcessor("org.projectlombok:lombok")
compileOnly("org.projectlombok:lombok")
}
package org.example.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class TodoEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
// order 키워드는 H2 데이터베이스에서 이미 예약어로 사용되기에 name을 새로 지정
@Column(name = "todoOrder", nullable = false)
private Long order;
@Column(nullable = false)
private Boolean completed;
}
package org.example.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodoRequest {
private String title;
private Long order;
private Boolean completed;
}
package org.example.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodoResponse {
private Long id;
private String title;
private Long order;
private Boolean completed;
private String url;
public TodoResponse(TodoEntity todoEntity) {
this.id = todoEntity.getId();
this.title = todoEntity.getTitle();
this.order = todoEntity.getOrder();
this.completed = todoEntity.getCompleted();
this.url = "http://localhost:8080/" + this.id;
}
}
package org.example.repository;
import org.example.model.TodoEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
// JpaRepository<데이터베이스와 연결될 객체, 해당 객체의 id의 필드 타입>
@Repository
public interface TodoRepository extends JpaRepository<TodoEntity, Long> {
}
package org.example.service;
import lombok.AllArgsConstructor;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.repository.TodoRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@AllArgsConstructor
public class TodoService {
private final TodoRepository todoRepository;
// 1 todo 리스트 목록에 아이템을 추가
public TodoEntity add(TodoRequest request) {
return null;
}
// 2 todo 리스트 목록 중 특정 아이템을 조회
public TodoEntity searchById(Long id) {
return null;
}
// 3 todo 리스트 전체 목록을 조회
public List<TodoEntity> searchAll() {
return null;
}
// 4 todo 리스트 목록 중 특정 아이템을 수정
public TodoEntity updateById(Long id) {
return null;
}
// 5 todo 리스트 목록 중 특정 아이템을 삭제
public void deleteById(Long id) {
}
// 6 todo 리스트 전체 목록을 삭제
public void deleteAll() {
}
}
package org.example.service;
import lombok.AllArgsConstructor;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.repository.TodoRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Service
@AllArgsConstructor
public class TodoService {
private final TodoRepository todoRepository;
// 1 todo 리스트 목록에 아이템을 추가
public TodoEntity add(TodoRequest request) {
TodoEntity todoEntity = new TodoEntity();
todoEntity.setTitle(request.getTitle());
todoEntity.setOrder(request.getOrder());
todoEntity.setCompleted(request.getCompleted());
// <S extends T> S save(S entity);
// save는 제네릭으로 받은 타입(T)으로 값을 반환
return this.todoRepository.save(todoEntity);
}
// 2 todo 리스트 목록 중 특정 아이템을 조회
public TodoEntity searchById(Long id) {
return this.todoRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
// 3 todo 리스트 전체 목록을 조회
public List<TodoEntity> searchAll() {
return this.todoRepository.findAll();
}
// 4 todo 리스트 목록 중 특정 아이템을 수정
public TodoEntity updateById(Long id, TodoRequest request) {
TodoEntity todoEntity = this.searchById(id);
if(request.getTitle() != null) {
todoEntity.setTitle(request.getTitle());
}
if(request.getOrder() != null) {
todoEntity.setOrder(request.getOrder());
}
if(request.getCompleted() != null) {
todoEntity.setCompleted(request.getCompleted());
}
return this.todoRepository.save(todoEntity);
}
// 5 todo 리스트 목록 중 특정 아이템을 삭제
public void deleteById(Long id) {
this.todoRepository.deleteById(id);
}
// 6 todo 리스트 전체 목록을 삭제
public void deleteAll() {
this.todoRepository.deleteAll();
}
}
package org.example.controller;
import lombok.AllArgsConstructor;
import org.example.model.TodoEntity;
import org.example.model.TodoResponse;
import org.example.service.TodoService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin
@AllArgsConstructor
@RestController
@RequestMapping("/")
public class TodoController {
private final TodoService service;
@PostMapping
public ResponseEntity<TodoResponse> create() {
System.out.println("CREATE");
return null;
}
@GetMapping("{id}")
public ResponseEntity<TodoResponse> readOne() {
System.out.println("READ ONE");
return null;
}
@GetMapping
public ResponseEntity<List<TodoEntity>> readAll() {
System.out.println("READ ALL");
return null;
}
@PatchMapping("{id}")
public ResponseEntity<TodoEntity> update() {
System.out.println("UPDATE");
return null;
}
@DeleteMapping("{id}")
public ResponseEntity<?> deleteOne() {
System.out.println("DELETE ONE");
return null;
}
@DeleteMapping
public ResponseEntity<?> deleteAll() {
System.out.println("DELETE ALL");
return null;
}
}
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TodoServerApplication {
public static void main(String[] args) {
SpringApplication.run(TodoServerApplication.class, args);
}
}
package org.example.controller;
import lombok.AllArgsConstructor;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.model.TodoResponse;
import org.example.service.TodoService;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@CrossOrigin
@AllArgsConstructor
@RestController
@RequestMapping("/")
public class TodoController {
private final TodoService service;
@PostMapping
public ResponseEntity<TodoResponse> create(@RequestBody TodoRequest request) {
System.out.println("CREATE");
if (ObjectUtils.isEmpty(request.getTitle()))
return ResponseEntity.badRequest().build();
if (ObjectUtils.isEmpty(request.getOrder()))
request.setOrder(0L);
if (ObjectUtils.isEmpty(request.getCompleted()))
request.setCompleted(false);
TodoEntity result = this.service.add(request);
return ResponseEntity.ok(new TodoResponse(result));
}
@GetMapping("{id}")
public ResponseEntity<TodoResponse> readOne(@PathVariable Long id) {
System.out.println("READ ONE");
TodoEntity result = this.service.searchById(id);
return ResponseEntity.ok(new TodoResponse(result));
}
@GetMapping
public ResponseEntity<List<TodoResponse>> readAll() {
System.out.println("READ ALL");
List<TodoEntity> list = this.service.searchAll();
List<TodoResponse> response = list.stream().map(TodoResponse::new)
.collect(Collectors.toList());
return ResponseEntity.ok(response);
}
@PatchMapping("{id}")
public ResponseEntity<TodoEntity> update(@PathVariable Long id, @RequestBody TodoRequest request) {
System.out.println("UPDATE");
TodoEntity result = this.service.updateById(id, request);
return ResponseEntity.ok(result);
}
@DeleteMapping("{id}")
public ResponseEntity<?> deleteOne(@PathVariable Long id) {
System.out.println("DELETE ONE");
this.service.deleteById(id);
return ResponseEntity.ok().build();
}
@DeleteMapping
public ResponseEntity<?> deleteAll() {
System.out.println("DELETE ALL");
this.service.deleteAll();
return ResponseEntity.ok().build();
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
annotationProcessor("org.projectlombok:lombok")
compileOnly("org.projectlombok:lombok")
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
package org.example.service;
import org.example.repository.TodoRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
// Test로 Mock객체를 사용
@ExtendWith(MockitoExtension.class)
class TodoServiceTest {
@Mock
private TodoRepository todoRepository;
@InjectMocks
private TodoService todoService;
@Test
void add() {
}
@Test
void searchById() {
}
}
package org.example.service;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.repository.TodoRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.AdditionalAnswers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
// Test로 Mock객체를 사용
@ExtendWith(MockitoExtension.class)
class TodoServiceTest {
@Mock
private TodoRepository todoRepository;
@InjectMocks
private TodoService todoService;
@Test
void add() {
// TodoRepository가 save 메소드를 호출해서 TodoEntity 값을 받으면
// 받은 Entity 값을 반환
when(this.todoRepository.save(any(TodoEntity.class)))
.then(AdditionalAnswers.returnsFirstArg());
TodoRequest expected = new TodoRequest();
expected.setTitle("Test Title");
TodoEntity actual = this.todoService.add(expected);
assertEquals(expected.getTitle(), actual.getTitle());
}
@Test
void searchById() {
}
}
package org.example.service;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.repository.TodoRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.AdditionalAnswers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
// Test로 Mock객체를 사용
@ExtendWith(MockitoExtension.class)
class TodoServiceTest {
@Mock
private TodoRepository todoRepository;
@InjectMocks
private TodoService todoService;
@Test
void add() {
// TodoRepository가 save 메소드를 호출해서 TodoEntity 값을 받으면
// 받은 Entity 값을 반환
when(this.todoRepository.save(any(TodoEntity.class)))
.then(AdditionalAnswers.returnsFirstArg());
TodoRequest expected = new TodoRequest();
expected.setTitle("Test Title");
TodoEntity actual = this.todoService.add(expected);
assertEquals(expected.getTitle(), actual.getTitle());
}
@Test
void searchById() {
// findById는 옵션널을 반환하기에 옵션널로 리턴값을 넣어준다
TodoEntity entity = new TodoEntity();
entity.setId(123L);
entity.setTitle("TITLE");
entity.setOrder(0L);
entity.setCompleted(false);
Optional<TodoEntity> optional = Optional.of(entity);
// 어떠한 id값이 주어졌을 때 optional 값을 리턴
given(this.todoRepository.findById(anyLong()))
.willReturn(optional);
// service에서 searchById를 했을 때 값을 받은 값과 optional 값을 비교
TodoEntity actual = this.todoService.searchById(123L);
TodoEntity expected =optional.get();
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getTitle(), actual.getTitle());
assertEquals(expected.getOrder(), actual.getOrder());
assertEquals(expected.getCompleted(), actual.getCompleted());
}
}
package org.example.service;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.repository.TodoRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.AdditionalAnswers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
// Test로 Mock객체를 사용
@ExtendWith(MockitoExtension.class)
class TodoServiceTest {
@Mock
private TodoRepository todoRepository;
@InjectMocks
private TodoService todoService;
@Test
void add() {
// TodoRepository가 save 메소드를 호출해서 TodoEntity 값을 받으면
// 받은 Entity 값을 반환
when(this.todoRepository.save(any(TodoEntity.class)))
.then(AdditionalAnswers.returnsFirstArg());
TodoRequest expected = new TodoRequest();
expected.setTitle("Test Title");
TodoEntity actual = this.todoService.add(expected);
assertEquals(expected.getTitle(), actual.getTitle());
}
@Test
void searchById() {
// findById는 옵션널을 반환하기에 옵션널로 리턴값을 넣어준다
TodoEntity entity = new TodoEntity();
entity.setId(123L);
entity.setTitle("TITLE");
entity.setOrder(0L);
entity.setCompleted(false);
Optional<TodoEntity> optional = Optional.of(entity);
// 어떠한 id값이 주어졌을 때 optional 값을 리턴
given(this.todoRepository.findById(anyLong()))
.willReturn(optional);
// service에서 searchById를 했을 때 값을 받은 값과 optional 값을 비교
TodoEntity actual = this.todoService.searchById(123L);
TodoEntity expected =optional.get();
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getTitle(), actual.getTitle());
assertEquals(expected.getOrder(), actual.getOrder());
assertEquals(expected.getCompleted(), actual.getCompleted());
}
@Test
public void searchByIdFailed() {
given(this.todoRepository.findById(anyLong()))
.willReturn(Optional.empty());
assertThrows(ResponseStatusException.class, () -> {
this.todoService.searchById(123L);
});
}
}
package org.example.controller;
import org.example.model.TodoEntity;
import org.example.service.TodoService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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;
@WebMvcTest(TodoController.class)
class TodoControllerTest {
@Autowired
MockMvc mvc;
@MockBean
TodoService todoService;
private TodoEntity expected;
// 테스트 진행 전 expected의 값 초기화
@BeforeEach
void setup() {
this.expected = new TodoEntity();
this.expected.setId(123L);
this.expected.setTitle("TEST TITLE");
this.expected.setOrder(0L);
this.expected.setCompleted(false);
}
@Test
void create() {
}
@Test
void readOne() {
}
}
package org.example.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.service.TodoService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(TodoController.class)
class TodoControllerTest {
@Autowired
MockMvc mvc;
@MockBean
TodoService todoService;
private TodoEntity expected;
// 테스트 진행 전 expected의 값 초기화
@BeforeEach
void setup() {
this.expected = new TodoEntity();
this.expected.setId(123L);
this.expected.setTitle("TEST TITLE");
this.expected.setOrder(0L);
this.expected.setCompleted(false);
}
@Test
void create() throws Exception{
// title만 request로 받아오고 나머지는 expected에 설정한 값 사용
when(this.todoService.add(any(TodoRequest.class)))
.then((i) -> {
TodoRequest request = i.getArgument(0, TodoRequest.class);
return new TodoEntity(this.expected.getId(),
request.getTitle(),
this.expected.getOrder(),
this.expected.getCompleted()
);
});
TodoRequest request = new TodoRequest();
request.setTitle("ANY TITLE");
// 작성한 request를 body에 넣어줘야 하는데 object type 자체만으로는 넣어줄 수 없기에
// objectmapper을 이용해 body에 넣어준다
ObjectMapper mapper = new ObjectMapper();
// request가 string 형태로 바뀜
String content = mapper.writeValueAsString(request);
this.mvc.perform(post("/")
.contentType(MediaType.APPLICATION_JSON)
.content(content))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value("ANY TITLE"));
}
@Test
void readOne() {
}
}