본 프로젝트는 “스프링부트 3 백엔드 개발자 되기” 서적을 참고하여 진행하였음
엔티티 구성
Table
Search
// domain > Article.java
package com.example.msblog.domain;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity // 엔티티로 지정
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
@Id // Id 필드를 기본키로 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키를 자동으로 1씩 증가시키기
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "title", nullable = false) // 'title' 이라는 not null 컬럼과 매핑
private String title;
@Column(name = "content", nullable = false)
private String content;
@Builder // 빌더 패턴으로 작성
public Article(String title, String content) {
this.title = title;
this.content = content;
}
}
Java
복사
리포지토리 만들기
// repository > BlogRepository
package com.example.msblog.repository;
import com.example.msblog.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BlogRepository extends JpaRepository<Article, Long> {
}
Java
복사
API 구현
•
DTO (Data Transfer Object)
// Dto > AddArticleRequest.java
package com.example.msblog.dto;
import com.example.msblog.domain.Article;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor // 기본 생성자 추가
@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자 추가
@Getter // Getter 안해주면 500 에러 발생하므로 주의!
public class AddArticleRequest {
private String title;
private String content;
public Article toEntity() { // 생성자를 사용하여 객체를 생성
return Article.builder()
.title(title)
.content(content)
.build();
}
}
Java
복사
•
Service
// service > BlogService.java
package com.example.msblog.service;
import com.example.msblog.domain.Article;
import com.example.msblog.dto.AddArticleRequest;
import com.example.msblog.repository.BlogRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service // 빈으로 등록
public class BlogService {
private final BlogRepository blogRepository;
// 블로그 글 추가 메서드
public Article save(AddArticleRequest request) {
return blogRepository.save(request.toEntity());
}
}
Java
복사
•
Controller
// controller > BlogApiController
package com.example.msblog.controller;
import com.example.msblog.domain.Article;
import com.example.msblog.dto.AddArticleRequest;
import com.example.msblog.service.BlogService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RestController // HTTP RESPONSE BODY에 객체 데이터를 JSON 형태로 반환
public class BlogApiController {
private final BlogService blogService;
// HTTP 메서드가 POST일 때 전달받은 URL과 동일하면 메서드로 매핑
@PostMapping("/api/articles")
// @RequestBody로 요청 본문 값 매핑
public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
Article savedArticle = blogService.save(request);
// 요청한 자원이 성공적으로 생성되었으며 저장된 블로그 글 정보를 응답 객체에 담아 전송
return ResponseEntity.status(HttpStatus.CREATED)
.body(savedArticle);
}
}
Java
복사
응답 코드 확인
Table
Search
최초 응답 테스트
•
POST 요청
{
"title": "title",
"content": "content"
}
JSON
복사
•
결과 - 요청한 POST 요청에 의해 데이터가 실제로 저장됨
{
"id": 1,
"title": "title",
"content": "content"
}
JSON
복사
테스트 코드 작성
Given | 블로그 글 추가에 필요한 요청 객체를 만든다. |
When | 블로그 글 추가 API에 요청을 보낸다. 요청 타입은 JSON 이며, give 절에서 미리 만들어준 객체를 요청 본문으로 함께 보낸다. |
Then | 응답 코드가 201 Created인지 확인한다. Blog를 전체 조회해 크기가 1인지 확인하고, 실제로 저장된 데이터와 요청 값을 비교한다. |
•
controller > BlogApiControllerTest
package com.example.msblog.controller;
import com.example.msblog.domain.Article;
import com.example.msblog.dto.AddArticleRequest;
import com.example.msblog.repository.BlogRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.util.List;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest // 테스트용 애플리케이션 컨택스트
@AutoConfigureMockMvc // MockMvc 생성 및 자동 구성
class BlogApiControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper; // 직렬화, 역직렬화를 위한 클래스
@Autowired
private WebApplicationContext context;
@Autowired
BlogRepository blogRepository;
@BeforeEach // 테스트 실행 전 실행하는 메서드
public void mockMvcSetUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
blogRepository.deleteAll();
}
@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
// mockMvc 활용하여 HTTP 메서드, URL, 요청 본문, 요청 타입 등을 설정한 뒤 설정한 내용을 바탕으로 테스트 요청을 보냄
// 설정한 내용 바탕으로 요청 전송
ResultActions result = mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_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);
}
}
Java
복사
•
테스트 코드 관련 참고내용
코드 | 설명 |
assertThat(articles.size()).isEqualTo(1); | 블로그 글 크기가 1이어야 한다. |
assertThat(articles.size()).isGreaterThan(2); | 블로그 글 크기가 2보다 커야 한다. |
assertThat(articles.size()).isLessThan(5); | 블로그 글 크기가 5보다 작아야 한다. |
assertThat(articles.size()).isZero(); | 블로그 글 크기가 0이어야 한다. |
assertThat(article.title()).isEqualTo(”제목”) | 블로그 글의 title값이 “제목”이어야 한다. |
assertThat(article.title()).isNotEmpty(); | 블로그 글의 title값이 비어 있지 않아야 한다. |
assertThat(article.title()).contains(”제”); | 블로그 글의 title값이 “제”를 포함해야 한다. |