Spring Service Test

김건우·2022년 12월 30일
0

Junit / TDD

목록 보기
2/4
post-thumbnail

Service Mockito Unit Test Method

  • @ExtendWith(MockitoExtension.class) : 테스트 클래스가 Mockito를 사용함을 의미한다.
  • @Mock : 실제 구현된 객체 대신에 Mock 객체를 사용하게 될 클래스를 의미한다. 테스트 런타임 시 해당 객체 대신 Mock 객체가 주입되어 Unit Test가 처리된다.
  • @InjectMocks : Mock 객체가 주입된 클래스를 사용하게 될 클래스를 의미합니다. 테스트 런타임 시 클래스 내부에 선언된 멤버 변수들 중에서 @Mock으로 등록된 클래스의 변수에 실제 객체 대신 Mock 객체가 주입되어 Unit Test가 처리된다.

PostService TEST (Mockito Unit)

@ExtendWith(MockitoExtension.class)
class PostServiceTest {


    @InjectMocks
    PostService postService;

    @Mock
    PostRepository postRepository ;
    @Mock
    UserRepository userRepository ;
    @Mock
    LikeRepository likeRepository;
}

위에서는 PostService 단을 테스트 하기위해서 클래스 상단에 @ExtendWith(MockitoExtension.class) 를 붙여주었고 @InjectMocks 어노테이션을 PostService에 적용하여 Mock객체가 주입 된 클래스를 사용하게 될 클래스를 명시해주었다. 그리고 마지막으로 @Mock 어노테이션이 붙은 Respository 클래스들은 테스트 런타임시에 PostService 단에 Mock객체가 주입된다.

TDD 적용

  • Given: 테스트를 위한 준비 과정이다. 변수를 선언하고, Mock 객체에 대한 정의도 함께 작성한다.
  • When: 테스트를 실행하는 과정이다. 테스트하고자 하는 내용을 작서한다.
  • Then: 테스트를 검증하는 과정이다. 예상한 값과 결괏값이 일치하는 지 확인한다.

Junit 테스트 픽스처란?

의역하자면, 테스트 실행을 위해 베이스라인으로서 사용되는 객체들의 고정된 상태이다. 테스트 픽스처의 목적은 결과를 반복가능할 수 있도록 알 수 있고, 고정된 환경에서 테스트할 수 있음을 보장하기 위함이다. 중복 발생되는 무언가(행위)를 고정시켜 한곳에 관리하도록 하겠다는 개념.

나는 postService단에서 많이 사용되는 객체들을 test 패키지 하위에 만들어줄 것이다.

1. User 와 Post의 정보를 담은 픽스처

serviceTest 클래스에서 다음과 같이 각각의 필드들의 정보를 임의로 값을 설정해 둔 객체

 @Data
    public static class PostAndUser{
        private Long postId;
        private Long userId;
        private String userName;
        private String password;
        private String title;
        private String body;
        //PostAndUser.getDto()라는 메소드를 사용하면 바로 값을 가져올 수 있다.
        public static PostAndUser getDto() {
            PostAndUser p = new PostAndUser();
            p.setPostId(1L);
            p.setUserId(1L);
            p.setUserName("test");
            p.setPassword("1234");
            p.setTitle("테스트 제목");
            p.setBody("테스트 내용");
            return p;
        }
    }

2.User엔티티 객체를 만들 픽스처

  • 매개변수 userName과 password에는 PostAndUser의 userName과 password를 넣어 줄 것이다.
public class  UserEntityFixture {

    public static User get(String userName,String password) {
      return  User.builder()
               .id(1l)
                .userName(userName)
                .password(password)
                .registeredAt(String.valueOf(Timestamp.from(Instant.now())))
                .role(UserRole.USER)
                .build();

    }
}

3. Post엔티티 객체를 만들 픽스처

  • 매개변수 user에는 UserEntityFixture의 엔티티를 넣어 줄 것이다.
public class PostEntityFixture {
    public static Post get(String userName, String password) {
        Post post = Post.builder()
                .id(1L)
                .user(UserEntityFixture.get(userName, password))
                .title("title")
                .body("body")
                .build();
        return post;
    }

    public static Post get(User user) {
        Post post = Post.builder()
                .id(1L)
                .user(user)
                .title("title")
                .body("body")
                .build();
        return post;
    }
}

글 조회 단건 service test

    @Test
    @DisplayName("조회 성공")
    void 글_조회_단건() {
        //postAndUser의 객체
        PostAndUser postAndUser = new PostAndUser();
        PostAndUser dto = postAndUser.getDto();
        
        //User 픽스처로 생성된 user엔티티(매개변수는 postAndUser픽스처의 값) 
        User user = UserEntityFixture.get(dto.getUserName(), dto.getPassword());
        //Post 픽스처로 생성된 user엔티티(매개변수는 User픽스처의 값) 
        Post post = PostEntityFixture.get(user);
        
        //postRepository에서 조회 메서드를 사용하면 post엔티티를 반환한다는 가정
        when(postRepository.findById(dto.getPostId())).thenReturn(Optional.of(post));
        //postService에서 포스트 단건조회 
        PostSelectResponse postSelectResponse = postService.getPost(dto.getPostId());
        //픽스처의 값과 service 반환 값의 테스트
        assertEquals(dto.getUserName(), postSelectResponse.getUserName());
    }

글 작성 service test 성공 / 실패

    @Nested
    @DisplayName("포스트 등록")
    class PostAdd {
        @Test
        @DisplayName("글 등록 성공 테스트")
        void 등록성공() {

            PostAndUser dto = PostAndUser.getDto();
            //User 픽스처로 생성된 user엔티티(매개변수는 postAndUser픽스처의 값)
            User user = UserEntityFixture.get(dto.getUserName(), dto.getPassword());
            //Post 픽스처로 생성된 user엔티티(매개변수는 User픽스처의 값)
            Post post = PostEntityFixture.get(user);
            //postservice의 작성에서 우선 회원을 찾을 것이다. userRepository.findOptionalByUserName
            when(userRepository.findOptionalByUserName(any())).thenReturn(Optional.of(user));
            //그 후 postRepository.save()를 적용
            when(postRepository.save(any())).thenReturn(post);
            //postService에 들어갈 변수
            PostAddRequest postAddRequest = new PostAddRequest(dto.getTitle(), dto.getBody());

            PostAddResponse postAddResponse = postService.addPost(postAddRequest, dto.getUserName());

            Assertions.assertEquals(postAddResponse.getPostId(), dto.getPostId());
            //assertDoesNotThrow() 이상이 없다면 통과
            Assertions.assertDoesNotThrow(() -> postAddResponse);
        }


        //포스트생성시 유저가 존재하지 않을 때 에러
        @Test
        @WithMockUser
        @DisplayName("등록 실패 : 유저 존재 하지 않음")
        void 등록_실패() {
            PostAndUser dto = PostAndUser.getDto();
            User user = UserEntityFixture.get(dto.getUserName(), dto.getPassword());
            Post post = PostEntityFixture.get(user);
            // 유저 존재 하지 않은 상황 가정
            when(userRepository.findOptionalByUserName(any())).thenReturn(Optional.empty());
            when(postRepository.save(any())).thenReturn(mock(Post.class));

            PostAddRequest postAddRequest = new PostAddRequest(dto.getTitle(), dto.getBody());

            UserException userException
                    = assertThrows(UserException.class, () -> postService.addPost(postAddRequest, dto.getUserName()));

            Assertions.assertEquals(userException.getErrorCode(), ErrorCode.USERNAME_NOT_FOUND);
            Assertions.assertEquals(userException.getMessage(),"회원가입 후 작성해주세요");
        }
    }
  • 테스트 결과 🔽

글 수정 service test 성공 / 실패

  @Nested
    @DisplayName("포스트 수정")
    class PostUpdate{
        
    @Test
    @DisplayName("수정 실패 : 작성자!=유저")
    void 수정_실패_작성자_불일치() {

        PostAndUser dto = PostAndUser.getDto();
        User user1 = UserEntityFixture.get("박지성", "password");
        User user2 = UserEntityFixture.get("손흥민", "password2");
        //user2가 작성한 post
        Post postByUser2 = PostEntityFixture.get(user2);
        //수정 request
        PostUpdateRequest postUpdateRequest = new PostUpdateRequest("수정될 제목", "수정될 내용");
        // post를 작성한 user는 user1
        when(postRepository.findById(dto.getPostId()))
                .thenReturn(Optional.of(Post.of("title","body",user1)));
        // post를 수정 시도한 user는 user2
        when(userRepository.findOptionalByUserName(any()))
                .thenReturn(Optional.of(User.of(user2.getUsername(), user2.getPassword())));

        UserException userException = assertThrows(UserException.class,
                () -> postService.updatePost(dto.getPostId(), postUpdateRequest, dto.getUserName()));

        //ErrorCode 검사
        Assertions.assertEquals(userException.getErrorCode(),ErrorCode.INVALID_PERMISSION);


    }

    @Test
    @DisplayName("수정 실패 : 포스트 존재하지 않음")
    @WithMockUser
    void 수정_실패_포스트_존재하지않음() {
        PostAndUser postAndUser = PostAndUser.getDto();
        //수정 request
        PostUpdateRequest postUpdateRequest = new PostUpdateRequest("수정될 제목", "수정될 내용");
        //해당 post존재하지 않음 Optional.of(mock(Post.class)) <- 이것을 넣으면 에러가 발생하면 안됨
        when(postRepository.findById(any())).thenReturn(Optional.empty());
        when(userRepository.findOptionalByUserName(any())).thenReturn(Optional.of(mock(User.class)));

        PostException postException =
                assertThrows(PostException.class,
                        () -> postService.updatePost(postAndUser.getPostId(), postUpdateRequest, postAndUser.getUserName()));

        Assertions.assertEquals(postException.getErrorCode(),ErrorCode.POST_NOT_FOUND);
    }

        @Test
        @DisplayName("수정 실패 : 회원 존재하지 않음")
        @WithMockUser
        void 수정_실패_회원_존재하지않음() {
            PostAndUser postAndUser = PostAndUser.getDto();
            //수정 request
            PostUpdateRequest postUpdateRequest = new PostUpdateRequest("수정될 제목", "수정될 내용");

            //해당 user존재 하지않는 상황가정
            when(postRepository.findById(any())).thenReturn(Optional.of(mock(Post.class)));
            when(userRepository.findOptionalByUserName(any())).thenReturn(Optional.empty());

            UserException userException =
                    assertThrows(UserException.class,
                            () -> postService.updatePost(postAndUser.getPostId(), postUpdateRequest, postAndUser.getUserName()));

            Assertions.assertEquals(userException.getErrorCode(),ErrorCode.USERNAME_NOT_FOUND);
        }


        @Test
        @DisplayName("포스트 수정 성공")
        void postUpdateSuccess() {

            PostAndUser postAndUser = PostAndUser.getDto();
            PostUpdateRequest postUpdateRequest = new PostUpdateRequest("수정될 제목", "수정될 내용");

            //수정 성공 로직
            when(userRepository.findOptionalByUserName(any())).thenReturn(Optional.of(mock(User.class)));
            when(postRepository.findById(any())).thenReturn(Optional.of(mock(Post.class)));

            PostUpdateResponse postUpdateResponse
                    = postService.updatePost(postAndUser.getPostId(), postUpdateRequest, postAndUser.getUserName());

            Assertions.assertEquals(postUpdateResponse.getMessage(),"포스트 수정 완료");

        }
    }
  • 테스트 결과 🔽

글 삭제 service test 성공 / 실패

    @Nested
    @DisplayName("포스트 삭제")
    class PostDelete{

        @Test
        @WithMockUser
        @DisplayName("포스트 삭제 성공")
        void 포스트_삭제_성공() {
            PostAndUser postAndUser = PostAndUser.getDto();
            User user = UserEntityFixture.get(postAndUser.getUserName(), postAndUser.getPassword());

            //수정 성공 로직
            when(userRepository.findOptionalByUserName(user.getUsername())).thenReturn(Optional.of(user));
            when(postRepository.findById(any())).thenReturn(Optional.of(PostEntityFixture.get(user)));

            PostDeleteResponse postDeleteResponse
                    = postService.deletePost(postAndUser.getPostId(), postAndUser.getUserName());

            Assertions.assertEquals(postDeleteResponse.getMessage(),"포스트 삭제 완료");
            Assertions.assertEquals(postDeleteResponse.getPostId(),1L);

        }


        @Test
        @DisplayName("포스트 삭제 작성자 불일치")
        void 삭제_실패_작성자_불일치() throws Exception {

            PostAndUser dto = PostAndUser.getDto();
            //유저1 (글 작성한 회원)
            User 유저1 = UserEntityFixture.get("유저1", "1234");
            //유저2 (글 삭제 시도 회원)
            User 유저2 = new User(2l, "유저2", "1234");

            when(postRepository.findById(any())).thenReturn(Optional.of(PostEntityFixture.get(유저1)));
            when(userRepository.findOptionalByUserName(any())).thenReturn(Optional.of(유저2));

            UserException userException =
                    assertThrows(UserException.class, () -> postService.deletePost(dto.getPostId(), dto.getUserName()));
            //에러 코드 확인
            Assertions.assertEquals(userException.getErrorCode(),ErrorCode.INVALID_PERMISSION);
        }

        @Test
        @WithMockUser
        @DisplayName("삭제 실패 : 포스트 존재 하지 않음")
        void 삭제_실패_포스트_존재x() throws Exception {
            PostAndUser dto = PostAndUser.getDto();
            //포스트 존재하지 않는 상황 가정
            when(postRepository.findById(any())).thenReturn(Optional.empty());

            PostException postException =
                    assertThrows(PostException.class, () -> postService.deletePost(dto.getPostId(), dto.getUserName()));

            Assertions.assertEquals(postException.getErrorCode(),ErrorCode.POST_NOT_FOUND);
            Assertions.assertEquals(postException.getMessage(),"해당 글은 존재하지 않아서 삭제할 수 없습니다.");
        }
    }
  • 테스트 결과🔽
profile
Live the moment for the moment.

0개의 댓글