관리 메뉴

bright jazz music

blog12: 예외처리1 본문

Projects/blog

blog12: 예외처리1

bright jazz music 2023. 1. 10. 23:06

자바에서 기본 제공하는 예외 말고 좀 더 명확하게 의미를 전달하는 고유의 예외 클래스를 만들어서 사용한다.

 

컨트롤러는 바뀐 것이 없다. PostService.java에서 수정한다.

//PostController.java

///...


@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 PostResponse get(@PathVariable Long postId){

        PostResponse response = postService.get(postId);
        return response;
    }

    //여러 글 조회(글 목록 가져오기)
    //  /posts
    @GetMapping("/posts")

    //글이 너무 많은 경우 비용이 너무 많이 든다.
    //DB가 뻗을 수 있음.
    //DB -> 애플리케이션 서버로 전달하는 시간, 트래픽 비용이 많이 발생할 수 있다.
    //따라서 페이지 설정

//    원래는 int로 받았음
//   public List<PostResponse> getList(@RequestParam int page){

// 그러나 사용의 용이성을 위해 pageable을 사용함
//   public List<PostResponse> getList(@PageableDefault Pageable pageable){ //1로 넘겨도 0으로 보정해서 넣어줌.
    //근데 PageableDefault의 기본 size가 10이라 yml에서 설정해도 먹히지 않는다.
    //이 떄는 어노테이션을 그대로 유지하면서 size를 파라미터로 넣어주는 방법이 있다.
    //public List<PostResponse> getList(@PageableDefault(size=10) Pageable pageable){

    //또는 어노테이션을 빼고 application.yml에서 default-page-size를 설정하여 해결할 수 수있다.

    //전에는 Pageable을 사용했으나 여러 요구사항을 수용 할 수 있는 클래스를 사용하기 위해
    //postSearch 클래스 사용


   public List<PostResponse> getList(@ModelAttribute PostSearch postSearch){//따로 만든 요청클래스 사용하려고 함
//   public List<PostResponse> getList(Pageable pageable){
        return postService.getList(postSearch);

    }

    @PatchMapping("/posts/{postId}")
    public void edit(@PathVariable Long postId, @RequestBody @Valid PostEdit request){
        postService.edit(postId, request);
    }

    @DeleteMapping("/posts/{postId}")
    public void delete(@PathVariable Long postId){
        postService.delete(postId);
    }

}

 

 

서비스.

.orElseThrow() 부분을 수정해준다.

//PostService.java

package com.endofma.blog.service;

import com.endofma.blog.domain.Post;
import com.endofma.blog.domain.PostEditor;
import com.endofma.blog.exception.PostNotFound;
import com.endofma.blog.repository.PostRepository;
import com.endofma.blog.request.PostCreate;
import com.endofma.blog.request.PostEdit;
import com.endofma.blog.request.PostSearch;
import com.endofma.blog.response.PostResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@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 PostResponse get(Long id) {

        Post post = postRepository.findById(id)
//                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글입니다.")); //있으면 post반환 없으면 에러 반환
//                .orElseThrow(() -> new PostNotFound());
                .orElseThrow(PostNotFound::new); //위와 같은 표현
        //또한 IllegalArgumentException과 같은 자바 기본제공 에러들은 우리의 에러를 명확히 표현해 주지는 못함.
        // 이렇게 처리해주면 굳이 test에서 전부 메시지를 확인할 필요가 없다. 이 에러가 발생했다면 글을 찾지 못했다는 뜻이기 때문이다.

        //응답 클래스를 분리
        return PostResponse.builder()
                .id(post.getId())
                .title(post.getTitle())
                .content(post.getContent())
                .build();

    }

//    public List<Post> getList() {
//        return postRepository.findAll();
//    }

//    public List<PostResponse> getList(Pageable pageable){
    public List<PostResponse> getList(PostSearch postSearch){

//        return postRepository.findAll(pageable).stream() //pageable
        return postRepository.getList(postSearch).stream() //QueryDsl사용
                .map(PostResponse::new)
                .collect(Collectors.toList());
    }

    //게시글 수정
    @Transactional //알아서 커밋
    public void edit(Long id, PostEdit postEdit){
        Post post = postRepository.findById(id)
//                .orElseThrow(()-> new IllegalArgumentException("존재하지 않는 글입니다."));
                .orElseThrow(PostNotFound::new);

//        post.setTitle(postEdit.getTitle());
//        post.setContent(postEdit.getContent());

//        post.change(postEdit.getTitle(), postEdit.getContent());

//        postRepository.save(post); 사실상 적어주지 않아도 된다. 대신 @Transactional을 사용

        PostEditor.PostEditorBuilder editorBuilder =  post.toEditor();

        PostEditor postEditor = editorBuilder
                .title(postEdit.getTitle())
                .content(postEdit.getContent())
                .build();

        post.edit(postEditor);

    }

    public void delete(Long id){
        Post post = postRepository.findById(id)
//                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글입니다."));
                .orElseThrow(PostNotFound::new);

        postRepository.delete(post);

    }

}

 

자바에서 제공하는 IllegalArgumentException 말고 PostNotFound.java를 만들어서 대신 메시지를 송출한다.

 

//PostNotFound.java

package com.endofma.blog.exception;

//public class PostNotFound extends Exception{
public class PostNotFound extends RuntimeException{ //uncheckedException
    //생성자 오버로딩을 해서 실제로 발생한 예외에게 메시지를 부여하면 된다.

    private static final String MESSAGE = "존재하지 않는 글입니다.";

    public PostNotFound(){
        super(MESSAGE);
    }

    public PostNotFound(Throwable cause){
        super(MESSAGE, cause);
    }
}

 

 

 

테스트.

test7, 8, 9 참조

//PostServiceTest.java

package com.endofma.blog.service;

import com.endofma.blog.domain.Post;
import com.endofma.blog.exception.PostNotFound;
import com.endofma.blog.repository.PostRepository;
import com.endofma.blog.request.PostCreate;
import com.endofma.blog.request.PostEdit;
import com.endofma.blog.request.PostSearch;
import com.endofma.blog.response.PostResponse;
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 org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

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
        PostResponse response = postService.get(requestPost.getId());

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

    @Test
    @DisplayName("글 1페이지 조회")
    void test3(){
        //given
        List<Post> requestPost = IntStream.range(1, 20) //for (int =0; i<30; i++)
                .mapToObj(i -> Post.builder()
                        .title("foo " + i)
                        .content("bar " + i)
                        .build())
                .collect(Collectors.toList());

        postRepository.saveAll(requestPost);


        // sql -> select, limit, offset 알아야 함.

//        Pageable pageableRequest = PageRequest.of(0, 5, Sort.Direction.DESC, "id");
        PostSearch postSearch = PostSearch.builder()
                .page(1)
                .size(10)
                .build();


        //when
        List<PostResponse> posts = postService.getList(postSearch);

       //then
        assertEquals(10L, posts.size());
        assertEquals("foo 19", posts.get(0).getTitle());

    }



    @Test
    @DisplayName("글 제목 수정")
    void test4(){
        //given
        Post post = Post.builder()
                .title("블로그 ")
                .content("제이드빌 ")
                .build();

        postRepository.save(post);

        PostEdit postEdit = PostEdit.builder()
                .title("블로그 수정")
                .content("제이드빌 ")
                .build();

        //when
        postService.edit(post.getId(), postEdit);

        //then
        Post chengedPost = postRepository.findById(post.getId())
                .orElseThrow(() -> new RuntimeException("글이 존재하지 않습니다. id=" + post.getId()));

        Assertions.assertEquals("블로그 수정", chengedPost.getTitle());
        Assertions.assertEquals("제이드빌 ", chengedPost.getContent());
    }

    @Test
    @DisplayName("글 제목 수정")
    void test5(){
        //given
        Post post = Post.builder()
                .title("블로그 ")
                .content("제이드빌 ")
                .build();

        postRepository.save(post);

        PostEdit postEdit = PostEdit.builder()
                .title("블로그 수정")
                .content("부림동 ")
                .build();

        //when
        postService.edit(post.getId(), postEdit);

        //then
        Post chengedPost = postRepository.findById(post.getId())
                .orElseThrow(() -> new RuntimeException("글이 존재하지 않습니다. id=" + post.getId()));

        Assertions.assertEquals("블로그 수정", chengedPost.getTitle());
        Assertions.assertEquals("부림동 ", chengedPost.getContent());
    }

    @Test
    @DisplayName("게시글 삭제")
    void test6(){
        //given
        Post post = Post.builder()
                .title("블로그 ")
                .content("제이드빌 ")
                .build();
        postRepository.save(post);

        //when
        postService.delete(post.getId());

        //then
        Assertions.assertEquals(0, postRepository.count());
    }

    @Test
    @DisplayName("글 1개 조회 - 존재하지 않는 글")
    void test7(){
        //given
        Post post = Post.builder()
                .title("블로그 ")
                .content("제이드빌 ")
                .build();
        postRepository.save(post);

        //when
//        IllegalArgumentException e = Assertions.assertThrows(IllegalArgumentException.class, () -> {
//            postService.get(post.getId() + 1L);
//        }); 앞서 적어준 예외를 처리하지 못하는 경우 메시지를 발생시킴

//        Assertions.assertEquals("존재하지 않는 글입니다.", e.getMessage());
        //메시지 반환측에서 메시지를 바꿀 때마다 테스트의 메시지를 전부 바꿔줘야 하는 문제가 있음.
        //또한 IllegalArgumentException과 같은 자바 기본제공 에러들은 우리의 에러를 명확히 표현해 주지는 못함.

        //expected
        Assertions.assertThrows(PostNotFound.class, () -> {
//        Assertions.assertThrows(IllegalArgumentException.class, () -> { //오류를 보고싶으면 이걸로 테스트
            postService.get(post.getId() + 1L);
        });
        //이렇게 처리해주면 굳이 test에서 전부 메시지를 확인할 필요가 없다. 이 에러가 발생했다면 글을 찾지 못했다는 뜻이기 때문이다.

    }

    @Test
    @DisplayName("게시글 삭제 - 존재하지 않는 글")
    void test8(){
        //given
        Post post = Post.builder()
                .title("블로그 ")
                .content("제이드빌 ")
                .build();
        postRepository.save(post);

        //then
        Assertions.assertThrows(PostNotFound.class, () -> {
            postService.delete(post.getId() + 1L);
        });
    }

    @Test
    @DisplayName("게시글 수정 - 존재하지 않는 글")
    void test9(){
        //given
        Post post = Post.builder()
                .title("블로그 ")
                .content("제이드빌 ")
                .build();
        postRepository.save(post);

        PostEdit postEdit = PostEdit.builder()
                .title("블로그 수정 ")
                .content("부림동 ")
                .build();

        //expected
        Assertions.assertThrows(PostNotFound.class, () -> {
            postService.edit(post.getId() + 1L, postEdit);
        });

    }



}

위에서는 postService.get(post.getId() + 1L) 등을 사용하여 존재하지 않는 글을 조회함으로써 오류를 발생시킬 수 있었다. 이 경우 테스트가 성공한다.

 

만약 존재하는 글을 조회하도록 postService.get(post.getId())를 사용한다면 테스트가 실패한다. 오류가 날 것을 기대하였지만 오류가 발생하지 않았기 때문이다. 이 경우 아래와 같은 로그가 생성된다.

 

Expected com.endofma.blog.exception.PostNotFound to be thrown, but nothing was thrown.
org.opentest4j.AssertionFailedError: Expected com.endofma.blog.exception.PostNotFound to be thrown, but nothing was thrown.
	at app//org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:71)
	at app//org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:37)
	at app//org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3082)

PostNotFound 예외가 발생할 것으로 기대하였지만 아무 것도 발생하지 않았다는 의미이다.

 

'Projects > blog' 카테고리의 다른 글

blog12: 예외처리 3  (0) 2023.01.14
blog12: 예외처리2  (0) 2023.01.13
blog11: 게시글 삭제  (0) 2023.01.10
blog10: 게시글 수정 2 (오류수정, 보충)  (0) 2023.01.09
blog10: 게시글 수정 1  (0) 2023.01.09
Comments