티스토리 뷰

Back-end/Spring Boot

Spring Data REST 테스트

jhkang-dev 2023. 3. 30. 00:31

안녕하세요 강정호입니다.

 

오늘은 Spring Data REST 복습을 해보려고 합니다.

 

Spring Data REST의 깃헙 프로젝트

https://github.com/spring-projects/spring-data-rest

 

GitHub - spring-projects/spring-data-rest: Simplifies building hypermedia-driven REST web services on top of Spring Data reposit

Simplifies building hypermedia-driven REST web services on top of Spring Data repositories - GitHub - spring-projects/spring-data-rest: Simplifies building hypermedia-driven REST web services on to...

github.com

 

[Spring Data REST란 무엇인가?]

Spring Data REST를 이해하기 위해서는 먼저 RESTful한 웹서비스가 무엇인지 알아야합니다.

RESTful 웹서비스 : HTTP 메서드를 통해서 해당 자원에 대한 CRUD 수행할 수 있는 서비스를 말한다.

 

그럼 기존의 RESTful 서비스와 Spring Data REST는 뭐가 더 좋은것인가?

기존에는 @RestController 어노테이션을 사용해서 컨트롤러에서 JSON 형식으로 데이터를 반환해주었습니다.

그리고 이 때 API를 RESTful하게 직접 지정을 해줘야 했습니다.

 

하지만!! Spring Data REST를 사용하면 별도의 컨트롤러 없이 Entity, Repository 인터페이스를 보고 자동으로 RESTful API를 제공해줍니다. 결국 알아서 생성해준다는게 편하닷!!!

 

[Spring Data REST를 사용하려면?]

Spring Data REST를 사용하기 위해서는 Entity와 Respository 설정이 필요하다.

 

Entity 예제

package com.marathon.board.domain;

import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Getter
@ToString
@Table(indexes = {
    @Index(columnList="title"),
    @Index(columnList="hashtag"),
    @Index(columnList="createdAt"),
    @Index(columnList="createdBy")
})
@Entity
public class Article extends AuditingFields {

  //본문 인덱스 : 본문검색에는 인덱스를 걸지 않는다. 너무 길어서 본문에는 인덱스 X. 그리고 인덱스에 용량이 한계가 있다.
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;


  @Setter
  @Column(nullable = false)
  private String title; // 제목
  @Setter
  @Column(nullable = false, length = 10000)
  private String content; // 본문
  @Setter
  private String hashTag; // 해시태그

  @OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
  private final Set<ArticleComment> articleComments = new LinkedHashSet<>();


  protected Article (){

  }

  private Article(String title, String content, String hashTag) {
    this.title = title;
    this.content = content;
    this.hashTag = hashTag;
  }

  public static Article of(String title, String content, String hashTag) {
    return new Article(title, content, hashTag);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof Article article)) {
      return false;
    }

    return id != null && id.equals(article.id);
  }

  @Override
  public int hashCode() {
    return Objects.hash(id);
  }
}

 

Respository 예제

package com.marathon.board.repository;

import com.marathon.board.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource
public interface ArticleRepository extends
    JpaRepository<Article, Long> ,
    QuerydslPredicateExecutor<Article>
    //QuerydslBinderCustomizer<QArticle>
{
}

위와 같이 @RespositoryRestResource 어노테이션을 붙여줘야 Spring Data REST 프레임워크에서 읽어서 API를 엔티티에 맞게 자동으로 생성해준다.

 

 

@DisplayName("Data REST 테스트")
@Transactional
@AutoConfigureMockMvc
@SpringBootTest
//@WebMvcTest 미사용 이유 확인하기.
public class DataRestTest {

    // WebMvc 테스트는 내부적으로 MockMvc를 사용할 수 있게 해준다.

    private final MockMvc mvc;

    public DataRestTest(@Autowired MockMvc mvc) {
        this.mvc = mvc;
    }

    @DisplayName("[api] 게시글 리스트 조회")
    @Test
    void givenNotion_whenRequestArticles_thenReturnArticlesJsonResponse() throws Exception {
        //Given

        //When & Then
        // 단축키1 : 컨트롤 + 스페이스 : MockBuilderGet 사용
        // 단축키2: 옵션 + 스페이스 => 항상 스태틱으로 가져오기
        mvc.perform(get("/api/articles"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.valueOf("application/hal+json")))
            .andDo(print());

        /**
         * 테스트 실패 이유
         * WebMvcTest는 슬라이스 테스트이다.
         * 컨트롤러 이외의 빈은 로드하지 않는다. 붎필요하다고 생각하는 빈은 생성하지 않는다
         * DataRest의 AutoConfiguration을 읽지 않는다
         * 그래서 가장 간단한 방법으로는 이 테스트를 IntegrationTest로 하는 것이다.
         * */

    }

    @DisplayName("[api] 게시글 단건 조회")
    @Test
    void givenArticleId_whenRequestArticles_thenReturnArticlesJsonResponse() throws Exception {
        //Given

        //When & Then
        // 단축키1 : 컨트롤 + 스페이스 : MockBuilderGet 사용
        // 단축키2: 옵션 + 스페이스 => 항상 스태틱으로 가져오기
        mvc.perform(get("/api/articles/1"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.valueOf("application/hal+json")))
            .andDo(print());
    }

    @DisplayName("[api] 게시글 댓글 조회")
    @Test
    void givenArticleId_whenRequestArticleComments_thenReturnArticlesCommentJsonResponse() throws Exception {
        //Given

        //When & Then
        // 단축키1 : 컨트롤 + 스페이스 : MockBuilderGet 사용
        // 단축키2: 옵션 + 스페이스 => 항상 스태틱으로 가져오기
        mvc.perform(get("/api/articles/1/articleComments"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.valueOf("application/hal+json")))
            .andDo(print());
    }

    @DisplayName("[api] 댓글 리스트 조회")
    @Test
    void givenNothing_whenRequestArticleComments_thenReturnArticlesCommentJsonResponse() throws Exception {
        //Given

        //When & Then
        // 단축키1 : 컨트롤 + 스페이스 : MockBuilderGet 사용
        // 단축키2: 옵션 + 스페이스 => 항상 스태틱으로 가져오기
        mvc.perform(get("/api/articleComments"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.valueOf("application/hal+json")))
            .andDo(print());
    }

    @DisplayName("[api] 댓글 단건 조회")
    @Test
    void givenNothing_whenRequestArticleComment_thenReturnArticleCommentJsonResponse() throws Exception {
        //Given

        //When & Then
        // 단축키1 : 컨트롤 + 스페이스 : MockBuilderGet 사용
        // 단축키2: 옵션 + 스페이스 => 항상 스태틱으로 가져오기
        mvc.perform(get("/api/articleComments/1"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.valueOf("application/hal+json")))
            .andDo(print());
    }
}

@Transactional 어노테이션

 

 

 

 

 

댓글