관리 메뉴

bright jazz music

blog05: 단건조회 (포스트 조회) 본문

Projects/blog

blog05: 단건조회 (포스트 조회)

bright jazz music 2022. 12. 29. 08:28

1. PostController에 get 메소드 추가

//PostController.java

package com.endofma.blog.controller;

import com.endofma.blog.domain.Post;
import com.endofma.blog.request.PostCreate;
import com.endofma.blog.service.PostService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Slf4j
@RestController
@RequiredArgsConstructor
public class PostController {
    private final PostService postService;


    @PostMapping("/posts")
    public void post(@RequestBody @Valid PostCreate request) {
        postService.write(request);
//        return Map.of();
    }

    //단건 조회
    @GetMapping("/posts/{postId}")
    public Post get(@PathVariable Long postId){
//    public void get(@PathVariable(name="postId") Long 임의변수명){ 이렇게 해도 된다.
        Post post = postService.get(postId);
        return post;
    }


}

 

2. PostService에 get 메서드 추가

//PostService.java

package com.endofma.blog.service;

import com.endofma.blog.domain.Post;
import com.endofma.blog.repository.PostRepository;
import com.endofma.blog.request.PostCreate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Slf4j
@Service
//@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;
    public PostService(PostRepository postRepository){
        this.postRepository = postRepository;
    }

    public void write(PostCreate postCreate) {
        //PostCreate 일반 클래스 ==> Post 엔티티
        Post post = Post.builder()
                .title(postCreate.getTitle())
                .content(postCreate.getContent())
                .build();

        postRepository.save(post);
    }

    //단건 조회
    public Post get(Long id) {
//        Post post = postRepository.findById(id); optional 데이터로 감싸져서 오기 때문에 아래와 같이 할 수 있다.
//        Optional<Post> postOptional = postRepository.findById(id);
//        if(postOptional.isPresent()){
//            return postOptional.get();
//        }
//        return null; 그러나 가능한 한 옵셔널은 바로 꺼내서 변환해 주는 게 좋다.

        Post post = postRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글입니다.")); //있으면 post반환 없으면 에러 반환
        return post;

    }
}

 

리포지토리는 구현한 JpaRepository 인터페이스의 것을 사용

//PostRepository.java

package com.endofma.blog.repository;

import com.endofma.blog.domain.Post;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Post, Long> { //Post 엔티티와 primary key 형식이 들어간다.

}

 

 

 

3. 서비스 테스트 : test2()

//PostServiceTest.java

package com.endofma.blog.service;

import com.endofma.blog.domain.Post;
import com.endofma.blog.repository.PostRepository;
import com.endofma.blog.request.PostCreate;
import org.junit.jupiter.api.Assertions;
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.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class PostServiceTest {

    @Autowired
    private PostService postService;

    @Autowired
    private PostRepository postRepository;

    @BeforeEach
    void clean(){
        postRepository.deleteAll();
    }

    @Test
    @DisplayName("글 작성")
    void test1() {
        //given
        PostCreate postCreate = PostCreate.builder()
                .title("제목입니다.")
                .content("내용입니다.")
                .build();

        //when
        postService.write(postCreate);

        //then
        Assertions.assertEquals(1L, postRepository.count());
        Post post = postRepository.findAll().get(0);
        assertEquals("제목입니다.", post.getTitle());
        assertEquals("내용입니다.", post.getContent());

    }

    @Test
    @DisplayName("글 1개 조회")
    void test2(){
        //given
        Post requestPost = Post.builder()
                .title("foo")
                .content("bar")
                .build();
        postRepository.save(requestPost);

        //when
        Post post = postService.get(requestPost.getId());

        //then
        Assertions.assertNotNull(post);
        assertEquals(1L, postRepository.count());
        assertEquals("foo", post.getTitle());
        assertEquals("bar", post.getContent());
    }

}

 

결과 : 테스트 클래스 전체 테스트를 수행해도 결과는 같다.

 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.6)

2022-12-29 16:19:04.846  INFO 43776 --- [    Test worker] c.endofma.blog.service.PostServiceTest   : Starting PostServiceTest using Java 11.0.12 on DESKTOP-8H1PTVG with PID 43776 (started by markany-hjcha in D:\personal\blog)
2022-12-29 16:19:04.851  INFO 43776 --- [    Test worker] c.endofma.blog.service.PostServiceTest   : No active profile set, falling back to 1 default profile: "default"
2022-12-29 16:19:05.425  INFO 43776 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-12-29 16:19:05.470  INFO 43776 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 38 ms. Found 1 JPA repository interfaces.
2022-12-29 16:19:05.884  INFO 43776 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-12-29 16:19:06.080  INFO 43776 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-12-29 16:19:06.147  INFO 43776 --- [    Test worker] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-12-29 16:19:06.206  INFO 43776 --- [    Test worker] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.14.Final
2022-12-29 16:19:06.377  INFO 43776 --- [    Test worker] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-12-29 16:19:06.505  INFO 43776 --- [    Test worker] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2022-12-29 16:19:07.125  INFO 43776 --- [    Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2022-12-29 16:19:07.133  INFO 43776 --- [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2022-12-29 16:19:07.613  WARN 43776 --- [    Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2022-12-29 16:19:08.099  INFO 43776 --- [    Test worker] c.endofma.blog.service.PostServiceTest   : Started PostServiceTest in 3.534 seconds (JVM running for 5.794)
2022-12-29 16:19:08.438  INFO 43776 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-29 16:19:08.439  INFO 43776 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2022-12-29 16:19:08.444  INFO 43776 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-12-29 16:19:08.449  INFO 43776 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 6s
4 actionable tasks: 1 executed, 3 up-to-date
PM 4:19:08: Execution finished ':test --tests "com.endofma.blog.service.PostServiceTest.test2"'.

 

4. 컨트롤러 테스트: test4()

//PostControllerTest.java

package com.endofma.blog.controller;

import com.endofma.blog.domain.Post;
import com.endofma.blog.repository.PostRepository;
import com.endofma.blog.request.PostCreate;
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.test.web.servlet.MockMvc;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; //이게 맞음
//import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath; 원래 이걸로 했음.


//@WebMvcTest //간단한 웹테스트만 가능. 우린 스프링 전반에 걸쳐 여러 가지를 만들었기 때문에 @SpringBootTest가 필요
@AutoConfigureMockMvc //@WebMvcTest가 없어지면 기존 테스트가 안되므로 @WebMvcTest를 구성하는 애노테이션을 떼내어 붙였다.
@SpringBootTest
class PostControllerTest {



    @Autowired
    private MockMvc mockMvc; ////Could not autowire. No beans of 'MockMvc' type found.
    
    @Autowired
    private PostRepository postRepository; //DB저장 테스트를 위해 주입

    @BeforeEach
    void clean() {
        postRepository.deleteAll();
    }

    @Test
    @DisplayName("/posts 요청시 hello world를 출력한다")
    void test() throws Exception {
        //given
//        PostCreate request = new PostCreate("제목입니다.", "내용입니다.");
        PostCreate request = PostCreate.builder()
                .title("제목입니다.")
                .content("내용입니다.")
                .build();

        //jackson을 사용하여 객체를 Json 형태로 바꿔준다.(json을 처리해 줌)
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(request);
//        @Autowired private ObjectMapper objectMapper; 이렇게 주입해서도 쓸 수 있다.

        System.out.println(json);

        //expected
        //기본적으로 Content-Type을 application/json으로 보냄. 예전에는 application/x-www-form-urlencoded를 썼다.
        mockMvc.perform(post("/posts")
                        .contentType(APPLICATION_JSON) //기본값이라 주석처리
//                        .content("{\"title\": \"제목입니다.\", \"content\": \"내용입니다.\"}") //json 형태로 값 넣어주기
                        //.content에는 byte나 String만 들어가진다. 따라서 jackson으로 Json변환처리를 하지 않은 request를 넣을 수는 없다.

                        .content(json) //jackson을 사용하여 json이 된 객체. 클래스에 게터가 존재해야 한다.
                )
                .andExpect(status().isOk()) //http response 가 200인지
                .andExpect(content().string("")) // 내용이 hello world인지
                .andDo(print()); //요청에 대한 전반적인 요약을 출력해준다.

    }


    @Test
    @DisplayName("/posts 요청시 title 값은 필수다")
    void test2() throws Exception {

        //given
        PostCreate request = PostCreate.builder()
                //.title("제목입니다.")
                .content("내용입니다.")
                .build();

        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(request);

        //expected
        mockMvc.perform(post("/posts")
                        .contentType(APPLICATION_JSON)
                                .content(json)

                )
                .andExpect(status().isBadRequest()) //.OK()
                .andExpect(jsonPath("$.code").value("400")) //json 검증
                .andExpect(jsonPath("$.message").value("잘못된 요청입니다!"))
//                .andExpect(jsonPath("$.validation.title").value("타이틀을 입력하세요!"))
                .andDo(print());

    }



    @Test
    @DisplayName("/posts 요청시 DB에 값이 저장된다.")
    void test3() throws Exception {
        //given
        PostCreate request = PostCreate.builder()
                .title("제목입니다.")
                .content("내용입니다.")
                .build();

        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(request);

        //when
        mockMvc.perform(post("/posts")
                                .contentType(APPLICATION_JSON)
                                .content(json)
                )
                .andExpect(status().isOk())
                .andDo(print());

        //then
        assertEquals(1L, postRepository.count()); //하나의 값이 있을 거라고 예상. 일치

        //DB에 잘 들어갔는지 확인
        Post post = postRepository.findAll().get(0); //가장 처음 데이터 가져옴
        assertEquals("제목입니다.", post.getTitle());
        assertEquals("내용입니다.", post.getContent());
    }

    @Test
    @DisplayName("글 1개 조회")
    void test4() throws Exception{
        //given
        Post post = Post.builder()
                .title("foo")
                .content("bar")
                .build();
        postRepository.save(post);

        //expected
        mockMvc.perform(get("/posts/{postId}", post.getId())
                        .contentType(APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(post.getId()))
                .andExpect(jsonPath("$.title").value("foo"))
                .andExpect(jsonPath("$.content").value("bar"))
                .andDo(print());
    }

}

 

결과 : 테스트 클래스 전체 테스트를 수행해도 결과는 같다.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.6)

2022-12-29 16:17:14.710  INFO 20612 --- [    Test worker] c.e.blog.controller.PostControllerTest   : Starting PostControllerTest using Java 11.0.12 on DESKTOP-8H1PTVG with PID 20612 (started by markany-hjcha in D:\personal\blog)
2022-12-29 16:17:14.711  INFO 20612 --- [    Test worker] c.e.blog.controller.PostControllerTest   : No active profile set, falling back to 1 default profile: "default"
2022-12-29 16:17:15.318  INFO 20612 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-12-29 16:17:15.360  INFO 20612 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 36 ms. Found 1 JPA repository interfaces.
2022-12-29 16:17:15.774  INFO 20612 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-12-29 16:17:15.968  INFO 20612 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-12-29 16:17:16.036  INFO 20612 --- [    Test worker] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-12-29 16:17:16.097  INFO 20612 --- [    Test worker] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.14.Final
2022-12-29 16:17:16.282  INFO 20612 --- [    Test worker] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-12-29 16:17:16.407  INFO 20612 --- [    Test worker] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2022-12-29 16:17:16.936  INFO 20612 --- [    Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2022-12-29 16:17:16.943  INFO 20612 --- [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2022-12-29 16:17:17.409  WARN 20612 --- [    Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2022-12-29 16:17:17.934  INFO 20612 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2022-12-29 16:17:17.934  INFO 20612 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
2022-12-29 16:17:17.935  INFO 20612 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 1 ms
2022-12-29 16:17:17.964  INFO 20612 --- [    Test worker] c.e.blog.controller.PostControllerTest   : Started PostControllerTest in 3.841 seconds (JVM running for 6.217)

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /posts/1
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8"]
             Body = null
    Session Attrs = {}

Handler:
             Type = com.endofma.blog.controller.PostController
           Method = com.endofma.blog.controller.PostController#get(Long)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"id":1,"title":"foo","content":"bar"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
2022-12-29 16:17:18.441  INFO 20612 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-29 16:17:18.442  INFO 20612 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2022-12-29 16:17:18.446  INFO 20612 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-12-29 16:17:18.450  INFO 20612 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 8s
4 actionable tasks: 2 executed, 2 up-to-date
PM 4:17:18: Execution finished ':test --tests "com.endofma.blog.controller.PostControllerTest.test4"'.
Comments