학습 목표 : 블로그 글을 만들고(create), 조회하고(read), 업데이트하고(update), 삭제하는(delete) RESTful API를 만들고 스프링 부트 3와 JPA를 어떻게 사용하는지 알아보기
** 목차 **
6.1 사전지식 : API와 REST API
6.2 블로그 개발을 위한 엔티티 구성하기
6.3 블로그 글 작성을 위한 API 구현하기
6.4 블로그 글 목록 조회를 위한 API 구현하기
6.5 블로그 글 조회 API 구현하기
6.6 블로그 글 삭제 API 구현하기
6.7 블로그 글 수정 API 구현하기
- API 구현 순서 : 서비스 메서드 코드 작성 → 컨트롤러 메서드 코드 작성 → 실행테스트 → 테스트 코드 작성
6.1 사전지식 : API와 REST API
1. 식당으로 알아보는 API
손님(클라이언트) ↔ 점원(API) ↔ 주방(서버)
ex) 웹 사이트에 방문하는 상황
: 우리는 웹 사이트의 주소를 입력해서 '구글 메인 화면을 보여줘'라고 요청
→ API는 이 요청을 받아서 서버에게 가져다 줌
→ 서버는 API가 준 요청을 처리해 결과물을 만들고 이것을 다시 API로 전달함
→ API는 최종 결과물을 브라우저에 보내주고 우리는 화면을 볼 수 있게 됨.
즉 API는 클라이언트의 요청을 서버에 잘 전달하고, 서버의 결과물을 클라이언트에게 잘 돌려주는 역할을 함.
2. 웹의 장점을 최대한 활용하는 REST API
REST(Representational State Transfer) : 자원을 이름으로 구분해 자원의 상태를 주고받는 API 방식, URL의 설계 방식
- REST API의 특징
: 서버/클라이언트 구조, 무상태, 캐시 처리 가능, 계층화, 인터페이스 일관성과 같은 특징
- REST API의 장점과 단점
장점 : URL만 보고도 무슨 행동을 하는 API인지 명확하게 알 수 있음.
상태가 없다는 특징 → 클라이언트와 서버의 역할이 명확하게 분리됨.
HTTP 표준을 사용하는 모든 플랫폼에서 사용할 수 있음.
단점 : HTTP 메서드(GET, POST와 같은 방식의 개수)에 제한이 있음.
설계를 하기 위해 공식적으로 제공되는 표준 규약이 없음.
- REST API를 사용하는 방법
1. URL에는 동사를 쓰지 말고, 자원을 표시해야 한다.
ex) 학생 중에 id가 1인 학생의 정보를 가져오는 URL
: /students/1, /get.student?student_id=1 → 둘 중 RESTful API는 '/students/1' (동사 있으면 안됨)
2. 동사는 HTTP 메서드로
HTTP 메서드 : 서버에 요청을 하는 방법을 나눈 것.
→ POST(만들기), GET(읽기), PUT(업데이트하기), DELETE(삭제하기)
→ 묶어서 CRUD라고 부름
ex) 블로그에 글을 쓰는 설계
GET/articles/1 : id가 1인 블로그 글을 조회하는 API
POST/articles/1 : 블로그 글을 추가하는 API
PUT/articles/1 : 블로그 글을 수정하는 API
DELETE/articles/1 : 블로그 글을 삭제하는 API
6.2 블로그 개발을 위한 엔티티 구성하기
1. 프로젝트 준비
2. 엔티티 구성하기
| 컬럼명 | 자료형 | null 허용 | 키 | 설명 |
| id | BIGINT | N | 기본키 | 일련번호, 기본키 |
| title | VARCHAR(255) | N | 게시물의 제목 | |
| content | VARCHAR(255) | N | 내용 |
만들 엔티티와 매핑되는 테이블의 구조
① src/main/java/me.cool/springbootdeveloper/domain/Article.java
@Entity // 엔티티로 지정
public class Article {
@Id // id 필드를 기본키로 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 자동으로 1씩 증가
private Long id;
@Column(name = "title", nullable = false) // 'title'이라는 not null 컬럼과 매핑
private String title;
@Column(name = "content", nullable = false) // 'content'라는 not null 컬럼과 매핑
private String content;
@Builder // 빌더 패턴으로 객체 생성
public Article(String title, String content){
this.title = title;
this.content = content;
}
// 기본 생성자
protected Article() {
}
// 게터
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
}
- @Builder 애너테이션 : 생성자 위에 입력하면 빌더 패턴 방식으로 객체를 생성할 수 있어 편리함.
어느 필드에 어떤 값이 들어가는지 명시적으로 파악할 수 있음.
→ @Builder 애너테이션을 사용하면 빌더 패턴을 사용하기 위한 코드를 자동으로 생성, 간편하게 빌더 패턴을 사용해 객체를 만들 수 있음.
// 빌더 패턴을 사용하지 않았을 때
new Article("abc", "def");
// 빌더 패턴을 사용했을 때
Article.builder()
.title("abc")
.content("def")
.build();
→ 빌더 패턴을 사용하면 어느 필드에 어느 값이 매칭되는지 바로 보이므로 객체 생성 코드의 가독성을 높일 수 있음
②
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "title", nullable = false)
private String title;
@Column(name = "content", nullable = false)
private String content;
@Builder
public Article(String title, String content) {
this.title = title;
this.content = content;
}
public void update(String title, String content) {
this.title = title;
this.content = content;
}
}
- getId(), getTitle(), getContent()와 같은 게터 메서드들은 @Getter 애너테이션으로 대치.
- @NoArgsConstructor 애너테이션을 선언해 접근 제어자가 protected인 기본 생성자를 별도의 코드 없이 생성.
3. Repository 만들기
src/main/java/me.cool/springbootdeveloper/repository/BlogRepository.java
public interface BlogRepository extends JpaRepository<Article, Long> {
}
- JpaRepository 클래스를 상속받을 때 엔티티 Article과 엔티티의 PK 타입 Long을 인수로 넣음.
6.3 블로그 글 작성을 위한 API 구현하기
클라이언트 ↔ 컨트롤러 ↔ 서비스 ↔ 리포지터리
1. 서비스 메서드 코드 작성하기
서비스 계층에서 요청을 받을 객체인 AddArticleRequest 객체 생성, BlogService 클래스 생성
→ 블로그 글 추가 메서드인 save() 구현
① src/main/java/me.cool/springbootdeveloper/dto/AddArticleRequest.java
- DTO(Data Transfer Object) : 계층끼리 데이터를 교환하기 위해 사용하는 객체
→ 단순하게 데이터를 옮기기 위해 사용하는 전달자 역할(별도의 비즈니스 로직 없음)
- DAO : 데이터베이스와 연결되고 데이터를 조회하고 수정하는데 사용하는 객체(데이터 수정과 관련된 로직 포함)
@NoArgsConstructor // 기본 생성자 추가
@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자 추가
@Getter
public class AddArticleRequest {
private String title;
private String content;
public Article toEntity() { // 생성자를 사용해 객체 생성
return Article.builder()
.title(title)
.content(content)
.build();
}
}
- toEntity()는 빌더 패턴을 사용해 DTO를 엔티티로 만들어주는 메서드
→ 이 메서드는 추후에 블로그 글을 추가할 때 저장할 엔티티로 변환하는 용도로 사용함.
② src/main/java/me.cool/springbootdeveloper/service/BlogService.java
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service // 빈으로 등록
public class BlogService {
private final BlogRepository blogRepository;
// 블로그 글 추가 메서드
public Article save(AddArticleRequest request) {
return blogRepository.save(request.toEntity());
}
}
- @RequiredArgsConstructor는 빈을 생성자로 생성하는 롬복에서 지원하는 애너테이션. final 키워드나 @NotNull이 붙은 필드로 생성자를 만들어줌.
- @Service 애너테이션은 해당 클래스를 빈으로 서블릿 컨테이너에 등록해줌.
- save() 메서드는 JpaRepository에서 지원하는 저장 메서드 save()로 AddArticleRequest 클래스에 저장된 값들을 article 데이터베이스에 저장함.
2. 컨트롤러 메서드 코드 작성하기
URL에 매핑하기 위한 컨트롤러 메서드 추가
URL 매핑 애너테이션 : @GetMapping, @PostMapping, @PutMapping, @DeleteMapping
/api/articles에 POST 요청이 오면 @PostMapping을 이용해 요청을 매핑
→ 블로그 글을 생성하는 BlogService의 save() 메서드를 호출
→ 생성된 블로그 글을 반환하는 작업을 할 addArticle() 메서드 작성
① src/main/java/me.cool/springbootdeveloper/controller/BlogApiController.java
@RequiredArgsConstructor
@RestController // HTTP Response Body에 객체 데이터를 JSON 형식으로 반환하는 컨트롤러
public class BlogApiController {
private final BlogService blogService;
// HTTP 메서드가 POST일 때 전달받은 URL와 동일하면 메서드로 매핑
@PostMapping("/api/articles")
// 요청 본문 값 매핑
public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
Article savedArticle = blogService.save(request);
// 요청한 자원이 성공적으로 생성되었으며 저장된 블로그 글 정보를 응답 객체에 담아 전송
return ResponseEntity.status(HttpStatus.CREATED)
.body(savedArticle);
}
}
- @RestController 애너테이션을 클래스에 붙이면 HTTP 응답으로 객체 데이터를 JSON 형식으로 반환함.
- @PostMapping() 애너테이션은 HTTP 메서드가 POST일 때 요청받은 URL와 동일한 메서드와 매핑함.
위 코드의 경우 /api/articles는 addArticle() 메서드에 매핑함.
- @RequestBody 애너테이션은 HTTP를 요청할 때 응답에 해당하는 값을 @RequestBody 애너테이션이 붙은 대상 객체인 AddArticleRequest에 매핑함.
- ResponseEntity.status().body() 는 응답 코드로 201, 즉 Created를 응답하고 테이블에 저장된 객체를 반환함.
3. API 실행 테스트하기
- H2 콘솔 활성화
① src/main/resources/application.yml
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
datasource:
url: jdbc:h2:mem:testdb
h2:
console:
enabled: true
② 스프링 부트 서버 실행
→ 포스트맨 실행
→ HTTP 메서드 : POST
URL : http://localhost:8080/api/articles
Body : JSON
요청 창 :
{
"title": "제목",
"content": "내용"
}
③ H2 데이터베이스에 잘 저장됐는지 확인
localhost:8080/h2-console 접속 → Connect
④ SQL statement: 입력창에 SELECT * FROM ARTICLE 입력 → Run
결과확인
: ARTICLE 이라는 테이블 존재.
애플리케이션을 실행하면 자동으로 생성한 엔티티 내용을 바탕으로 테이블이 생성되고, 우리가 요청한 POST 요청에 의해 데이터가 실제로 저장됨.
4. 반복 작업을 줄여 줄 테스트 코드 작성하기
① src/test/java/me.cool/springbootdeveloper/controller/BlogApiControllerTest.java
@SpringBootTest // 테스트용 애플리케이션 컨텍스트
@AutoConfigureMockMvc // MockMvc 생성
class BlogApiControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper; // 직렬화, 역직렬화를 위한 클래스
@Autowired
private WebAppicationContext context;
@BeforeEach // 테스트 전 실행하는 메서드
public void mockMvcSetUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
blogRepository.deleteAll();
}
}
- ObjectMapper 클래스로 만든 객체는 자바 객체를 JSON 데이터로 변환하는 직렬화 또는 반대로 JSON 데이터를 자바에서 사용하기 위해 자바 객체로 변환하는 역직렬화할 때 사용함.
② 블로그 글 생성 API 테스트 코드 작성
given : 블로그 글 추가에 필요한 요청 객체를 만든다.
when : 블로그 글 추가 API에 요청을 보낸다. 이때 요청 타입은 JSON이며, given절에서 미리 만들어둔 객체를 요청 본문으로 함께 보낸다.
then : 응답 코드가 201 Created인지 확인한다. Blog를 전체 조회해 크기가 1인지 확인하고, 실제로 저장된 데이터와 요청 값을 비교한다.
@SpringBootTest
@AutoConfigureMockMvc
class BlogApiControllerTest {
// 생략
@DisplayName("addArticle: 블로그 글 추가에 성공한다.")
@Test
public void addArticle() throws Exception {
// given
final String url = "/api/articles";
final String title = "title";
final String content = "content";
final AddArticleRequest userRequest = new AddArticleRequest(title, content);
// 객체 JSON으로 직렬화
final String requestBody = objectMapper.writeValueAsString(userRequest);
// when
// 설정한 내용을 바탕으로 요청 전송
ResultActions result = mockMvc.perform(post(url)
.contentType(MediaType.APPICATION_JSON_VALUE)
.content(requestBody));
// then
result.andExpect(status().isCreated());
List<Article> articles = blogRepository.findAll();
assertThat(articles.size()).isEqualTo(1); // 크기가 1인지 검증
assertThat(articles.get(0).getTitle()).isEqualTo(title);
assertThat(articles.get(0).getContent()).isEqualTo(content);
}
}
- writeValueAsString() 메서드를 사용해서 객체를 JSON으로 직렬화해줌.
- MockMvc를 사용해 HTTP 메서드, URL, 요청 본문, 요청 타입 등을 설정한 뒤 설정한 내용을 바탕으로 테스트 요청을 보냄.(HTTP 메서드 : post, 요청 타입: JSON)
③ 테스트 실행
6.4 블로그 글 목록 조회를 위한 API 구현하기
블로그 글 조회하기 위한 API 구현 - 모든 글을 조회하는 API, 글 내용을 조회하는 API
1. 서비스 메서드 코드 작성하기
① src/main/java/me.cool/springbootdeveloper/service/BlogService.java
@RequiredArgsConstructor
@Service
public class BlogService {
private final BlogRepository blogRepository;
// 생략
public List<Article> findAll() {
return blogRepository.findAll();
}
}
- JPA 지원 메서드인 findAll()을 호출해 article 테이블에 저장되어 있는 모든 데이터를 조회함.
2. 컨트롤러 메서드 코드 작성하기
/api/articles GET 요청이 오면 글 목록을 조회할 findAllArticles() 메서드
→ 전체 글 목록을 조회하고 응답하는 역할
① src/main/java/me.cool/springbootdeveloper/dto/ArticleResponse.java
@Getter
public class ArticleResponse {
private final String title;
private final String content;
public ArticleResponse(Article article) {
this.title = article.getTitle();
this.content = article.getContent();
}
}
- 글은 제목과 내용 구성이므로 해당 필드를 가지는 클래스를 만든 다음, 엔티티를 인수로 받는 생성자 추가
② src/main/java/me.cool/springbootdeveloper/controller/BlogApiController.java
- 파일을 열어 전체 글을 조회한 뒤 반환하는 findAllArticles() 메서드 추가
@RequiredArgsConstructor
@RestController
public class BlogApiController {
private final BlogService blogService;
// 생략
@GetMapping("/api/articles")
public ResponseEntity<List<ArticleResponse>> findAllArticles() {
List<ArticleReponse> articles = blogService.findAll()
.stream()
.map(ArticleResponse::new)
.toList();
return ResponseEntity.ok()
.body(articles);
}
}
- /api/articles GET 요청이 오면 글 전체를 조회하는 findAll() 메서드 호출
→ 응답용 객체인 ArticleResponse로 파싱해 body에 담아 클라이언트에게 전송함, 이 코드에는 스트림 적용함
3. 실행 테스트하기
① src/main/resource/data.sql
INSERT INTO article (title, content) VALUES ('제목1', '내용1')
INSERT INTO article (title, content) VALUES ('제목2', '내용2')
INSERT INTO article (title, content) VALUES ('제목3', '내용3')
② 포스트맨
HTTP 메서드 : GET
Params → URL : http://localhost:8080/api/articles → Send
4. 테스트 코드 작성하기
given : 블로그 글을 저장한다
when : 목록 조회 API를 호출한다
then : 응답 코드가 200 OK이고, 반환받은 값 중에 0번째 요소의 content와 title이 저장된 값과 같은지 확인한다
@SpringBootTest
@AutoConfigureMockMvc
class BlogApiControllerTest {
// 생략
@DisplayName("findAllArticles: 블로그 글 목록 조회에 성공한다.")
@Test
public void findAllArticles() throws Exception {
// given
final String url = "/api/articles";
final String title = "title";
final String content = "content";
blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
// when
final ResultActions resultActions = mockMvc.perform(get(url)
.accept(MediaType.APPLICATION_JSON));
// then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].content").value(content))
.andExpect(jsonPath("$[0].title").value(title));
}
}
6.5 블로그 글 조회 API 구현하기
글 하나를 조회하는 API 구현
1. 서비스 메서드 코드 작성
① src/main/java/me.cool/springbootdeveloper/service/BlogService.java
@RequiredArgsConstructor
@Service
public class BlogService {
// 생략
public Article findById(long id) {
return blogRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("not found: " + id));
}
}
- findById() 메서드는 JPA에서 제공하는 findById() 메서드를 사용해 ID를 받아 엔티티를 조회하고, 없으면 IllegalArgumentException 예외를 발생함.
2. 컨트롤러 메서드 코드 작성
① src/main/java/me.cool/springbootdeveloper/controller/BlogApiController.java
@RequiredArgsConstructor
@RestController
public class BlogApiController {
// 생략
@GetMapping("/api/articles/{id}")
// URL 경로에서 값 추출
public ResponseEntity<ArticleResponse> findArticle(@PathVariable long id) {
Article article = blogService.findById(id);
return ResponseEntity.ok()
.body(new ArticleResoonse(article));
}
}
- @PathVariable 애너테이션은 URL에서 값을 가져오는 애너테이션.
- 이 애너테이션이 붙은 메서드의 동작 원리
: /api/articles/3 GET 요청을 받으면 id에 3이 들어옴.
→ 이 값은 앞서 만든 서비스 클래스의 findById() 메서드로 넘어가 3번 블로그 글을 찾음.
→ 글을 찾으면 3번 글의 정보를 body에 담아 웹 브라우저로 전송함.
3. 테스트 코드 작성하기
given : 블로그 글을 저장한다
when : 저장한 블로그 글의 id 값으로 API를 호출한다
then : 응답 코드가 200 OK이고, 반환받은 content와 title이 저장된 값과 같은지 확인한다
① src/test/java/me.cool/springbootdeveloper/controller/BlogApiControllerTest.java
@SpringBootTest
@AutoConfigureMockMvc
class BlogApiControllerTest {
// 생략
@DisplayName("findArticle: 블로그 글 조회에 성공한다.")
@Test
public void findArticle() throws Exception {
// given
final String url = "/api/articles/{id}";
final String title = "title";
final String content = "content";
Article savedArticle = blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
// when
final ResultActions resultActions = mockMvc.perform(get(url, savedArticle.getId()));
// then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").value(content))
.andExpect(jsonPath("$.title").value(title));
}
}
6.6 블로그 글 삭제 API 구현하기
1. 서비스 메서드 코드 작성하기
① src/main/java/me.cool/springbootdeveloper/service/BlogService.java
@RequiredArgsConstructor
@Service
public class BlogService {
// 생략
public void delete(long id){
blogRepository.deleteById(id);
}
}
2. 컨트롤러 메서드 코드 작성하기
① src/main/java/me.cool/springbootdeveloper/controller/BlogApiController.java
@RequiredArgsConstructor
@RestController
public class BlogApiController {
// 생략
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Void> deleteArticle(@PathVariable long id){
blogService.delete(id);
return ResponseEntity.ok()
.build();
}
}
- /api/articles/{id} DELETE 요청이 오면 {id}에 해당하는 값이 @PathVariable 애너테이션을 통해 들어옴
3. 실행 테스트하기
① 블로그 글 삭제
포스트맨 실행
HTTP 메서드 : DELETE
URL : http://localhost:8080/api/articles/1
→ Send
② 블로그 글 조회
HTTP 메서드 : GET
URL : http://localhost:8080/api/articles
→ ID가 1인 글은 삭제됨을 확인할 수 있음
4. 테스트 코드 작성하기
given : 블로그 글을 저장한다
when : 저장한 블로그 글의 id 값으로 삭제 API를 호출한다
then : 응답 코드가 200 OK이고, 블로그 글 리스트를 전체 조회해 조회한 배열 크기가 0인지 확인한다
@SpringBootTest
@AutoConfigureMockMvc
class BlogApiControllerTest {
// 생략
@DisplayName("deleteArticle: 블로그 글 삭제에 성공한다.")
@Test
public void deleteArticle() {
// given
final String url = "/api/articles/{id}";
final String title = "title";
final String content = "content";
Article savedArticle = blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
// when
mockMvc.perform(delete(url, savedArticle.getId()))
.andExpect(status().isOk());
// then
List<Article> articles = blogRepository.findAll();
assertThat(articles).isEmpty()
}
}
6.7 블로그 글 수정 API 구현하기
1. 서비스 메서드 코드 작성하기
① src/main/java/me.cool/springbootdeveloper/domain/Article.java
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class Article{
// 생략
public void update(String title, String content){
this.title = title;
this.content = content;
}
}
② src/main/java/me.cool/springbootdeveloper/dto/UpdateArticleRequest.java
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class UpdateArticleRequest {
private String title;
private String content;
}
- 블로그 글 수정 요청을 받을 DTO
③ src/main/java/me.cool/springbootdeveloper/service/BlogService.java
@RequiredArgsConstructor
@Service
public class BlogService {
// 생략
@Transactional // 트랜잭션 메서드
public Article update(long id, UpdateArticleRequest request) {
Article article = blogRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("not found: " + id));
article.updqte(request.getTitle(), request.getContent());
return article;
}
}
- @Transactional 애너테이션은 매칭한 메서드를 하나의 트랜잭션으로 묶는 역할을 함.
→ update() 메서드는 엔티티의 필드 값이 바뀌면 중간에 에러가 발생해도 제대로 된 값 수정을 보장하게 되었음.
2. 컨트롤러 메서드 코드 작성하기
① src/main/java/me.cool/springbootdeveloper/controller/BlogApiController.java
@RequiredArgsConstructor
@RestController
public class BlogApiController {
// 생략
@PutMapping("/api/articles/{id}")
public ResponseEntity<Article> updateArticle(@PathVariable long id,
@RequestBody UpdateArticleRequest request) {
Article updatedArticle = blogService.update(id, request);
return ResponseEntity.ok()
.body(updateArticle);
}
}
- /api/articles/{id} PUT 요청이 오면 Request Body 정보가 request로 넘어옴.
- 다시 서비스 클래스의 update() 메서드에 id와 request를 넘겨줌.
- 응답 값은 body에 담아 전송함.
3. 실행 테스트하기
① 블로그 글 수정
포스트맨 실행
HTTP 메서드 : PUT
URL : http://localhost:8080/api/articles/1
Body(수정 내용 입력) :
{ "title": "새 블로그", "content": "새로운 글을 써볼까?" }
→ Send
② 글 전체 조회 API로 블로그 글 수정 확인
HTTP 메서드 : GET
4. 테스트 코드 작성하기
given : 블로그 글을 저장하고, 블로그 글 수정에 필요한 요청 객체를 만든다
when : UPDATE API로 수정 요청을 보낸다. 이때 요청 타입은 JSON이며, given절에서 미리 만들어둔 객체를 요청 본문으로 함께 보낸다
then : 응답 코드가 200 OK인지 확인한다. 블로그 글 id로 조회한 후에 값이 수정되었는지 확인한다
① src/test/java/me.cool/springbootdeveloper/controller/BlogApiControllerTest.java
@SpringBootTest
@AutoConfigureMockMvc
public class BlogApiControllerTest {
// 생략
@DisplayName("updateArticle: 블로그 글 수정에 성공한다.")
@Test
public void updateArticle() throws Exception {
// given
final String url = "/api/articles/{id}";
final String title = "title";
final String content = "content";
Article savedArticle = blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
final String newTitle = "new title";
final String newContent = "new content";
UpdateArticleRequest request = new UpdateArticleRequest(newTitle, newContent);
// when
ResultActions result = mockMvc.perform(put(url, savedArticle.getId())
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writewValueAsString(request)));
// then
result.andExpect(status.isOk());
Article article = blogRepository.findById(savedArticle.getId().get());
assertThat(article.getTitle()).isEqualTo(newTitle);
assertThat(article.getContent()).isEqualTo(newContent);
}
}
'Backend > <스프링 부트 3 백엔드 개발자 되기>' 카테고리의 다른 글
| 09장 JWT로 로그인/로그아웃 구현하기 (2) | 2023.11.04 |
|---|---|
| 08장 스프링 시큐리티로 로그인/로그아웃, 회원 가입 구현하기 (0) | 2023.11.03 |
| 05장 데이터베이스 조작이 편해지는 ORM (3) | 2023.11.01 |
| 04장 스프링 부트 3와 테스트 (1) | 2023.10.31 |
| 03장 스프링 부트 3 구조 이해하기 (0) | 2023.10.29 |