[TIL] 23.01.27

hyewon jeong·2023년 1월 27일
0

TIL

목록 보기
77/138

1 . 문제점

기존 프로젝트에 mockito를 이용한 테스트 코드 작성


2 . 시도한 점

mockito를 적용 하여 테스트 함


3 . 해결

@ExtendWith(MockitoExtension.class)
class PostServiceTest {
    @Mock
    PostRepository postRepository;

    @InjectMocks
    PostService postService;

    @Test
    void createPost() {
        //given
        PostRequestDto request = new PostRequestDto();
        User user = new User();
        //when
        postService.createPost(request, user);
        //then
        verify(postRepository, times(1)).save(any(Post.class));
    }

    @Test
    void getPosts() {
        //given
        Pageable pageable = Pageable.ofSize(20);
        when(postRepository.findAll(pageable))
                .thenReturn(Page.empty());
        //when
        Page<PostResponseDto> response = postService.getPosts(pageable);
        //then
        // assertThat(response).isEmpty(); //todo isEmpty 에러 생겨서 주석처리
        verify(postRepository, times(1)).findAll(pageable);

    }

    @Test
    @DisplayName("게시글 조회")
    void getPostById() {
        //given input return
        PostRequestDto request = PostRequestDto.builder()
                .contents("rara")
                .title("roro")
                .build();

        Post post = new Post(request, new User());

        when(postRepository.findById(anyLong()))
                .thenReturn(Optional.of(post));

        //when
        PostResponseDto response = postService.getPostById(anyLong());
        //then
        assertThat(response.getContents()).isEqualTo(request.getContents());
        assertThat(response.getTitle()).isEqualTo(request.getTitle());

        verify(postRepository, times(1)).findById(anyLong());

    }

    @Test
    @DisplayName("게시글 id가 없을때 예외 발생")
    void getPostById_throw() {
        // given
        when(postRepository.findById(anyLong())).thenReturn(Optional.empty());

        // when & then
        assertThrows(IllegalArgumentException.class, () -> {
            postService.getPostById(0L);
        });
    }

    @Test
    @DisplayName("게시글 업데이트")
    void updatePost() {
        //given
        PostRequestDto request = PostRequestDto.builder()
                .contents("roro")
                .title("roro")
                .build();
        User user = new User("pororo", "12345678", null, null);
        Post post = new Post(request, user);

        when(postRepository.findById(anyLong()))
                .thenReturn(Optional.of(post));
        //when
        postService.updatePost(anyLong(), request, user);
        //then
        verify(postRepository, times(1)).save(any(Post.class));
    }

    @Test
    @DisplayName("게시글 없데이트 (다른사람이 작성하려고 할때")
    void uadatePost_invaild_user() {
        //given
        PostRequestDto request = PostRequestDto.builder()
                .contents("rororo")
                .title("roro")
                .build();
        User user = new User("pororo","12345678",null,null);
        User user1 = new User("rupi","12345678",null,null);
        Post post = new Post(request,user);
        //return mocking
        when(postRepository.findById(anyLong()))
                .thenReturn(Optional.of(post));

        //when&then
        assertThrows(IllegalArgumentException.class, () -> {
            postService.updatePost(anyLong(), request, user1);
        });
    }
}
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;
    @InjectMocks
    private UserService userService;
    @Mock
    private JwtUtil jwtUtil;
    @Spy
    private BCryptPasswordEncoder passwordEncoder;

    @Test
    @DisplayName("로그인")
    void login() {
        //given
        //입력값
        LoginRequest request = LoginRequest.builder()
                .username("pororo")
                .password("pororo1234")
                .build();
        MockHttpServletResponse servletResponse = new MockHttpServletResponse();
        //리턴값
        User user = new User("pororo", passwordEncoder.encode("pororo1234"),null, null);
        when(userRepository.findByUsername(any(String.class)))
                .thenReturn(Optional.of(user));

        //when
           ResponseStatusDto response  = userService.login(request,servletResponse);
        //then
        assertThat(response.getStatusCode()).isEqualTo(StatusEnum.LOGIN_SUCCESS.getStatusCode());
        assertThat(response.getMsg()).isEqualTo(StatusEnum.LOGIN_SUCCESS.getMsg());
        assertThat(servletResponse.getHeaderValue("Authorization").toString()).isNotEmpty(); //리턴이 있는 결과값 검증
        //verify는 목객체를 인자로 받아, 해당 Mock 객체의 원하는 상호작용이 있었는가 검증
        verify(userRepository,times(1)).saveAndFlush(any(User.class)); //주요한 기능 함수 행위 검증할때 (마지막에 있는 로직으로 로직이 잘 돌아갔는지 확인하기위함)

    }

4 . 알게 된점

JUnit5, Hamcrest 및 Mockito

JUnit5:
Hamcrest: JUnit의 테스트 작성을 보다 문맥적으로 자연스럽고 우아하게 할 수 있도록 도와주는 Matcher 라이브러리입니다. JUnit5부터는 Hamcrest 관련 라이브러리가 포함되어 있지 않습니다.

Mockito: Java 프로그래밍에서의 단위 테스트를 위한 Mocking 오픈소스 프레임워크입니다.

Gradle에 Hamcrest, Mockito 라이브러리 Dependency 추가
Intellij IDEA 최신버전에서 Gradle을 이용하여 프로젝트를 구성하면 기본적으로 JUnit5 의존성을 가집니다. JUnit5부터는 Hamcrest 관련 라이브러리가 분리되었으므로 이를 Gradle 의존성에 추가해야 하며, JUnit5에서 Mockito를 함께 사용하기 위한 의존성도 함께 작성해주어야 합니다. 또한, 예제에서는 개발 편의를 위해 Lombok을 함께 사용합니다. 생성한 프로젝트의 build.gradle을 다음과 같이 수정하고, ‘Gradle Reload’ 작업을 수행하도록 합니다.** 다만, Spring Boot (2.2+) 프로젝트를 구성한 경우에는 ‘org.springframework.boot:spring-boot-starter-test’ 라이브러리에 이미 JUnit5, Hamcrest 및 Mockito가 모두 포함되어 있으므로 이 작업이 필요 없습니다.

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

이를 토대로 위의 코드를 쉽게 분석할 수 있습니다. UserServiceTest는 Mockito를 사용(@ExtendWith)하고, UserRepository를 실제 객체가 아닌 Mock 객체로 바꾸어 주입(@Mock)합니다. 따라서 테스트 런타임 시 UserService의 멤버 변수로 선언된 UserRepository에 Mock 객체가 주입(InjectMocks)됩니다.

Given – When – Then 패턴을 이용하여 Mock Test를 구성합니다.

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

assertThrows

  • assertThrows(Class<> classType, Executable executable)
  • assertThrows 메소드는 첫번째 인자로 발생할 예외 클래스의 Class 타입을 받습니다.
  • Assertions.assertThrows의 두 번째 인자인 입력된 함수? 를 실행하여 첫 번째 인자인 예외 타입과 같은지(혹은 캐스팅이 가능한 상속 관계의 예외인지) 검사합니다.

assertThat(결과 검증)

  • 리턴이 있는 결과값 검증

verify(행위 검증)

  • 목객체를 인자로 받아, 해당 Mock 객체의 원하는 상호작용이 있었는가 검증
  • 만약에 리턴갑싱 없으면 중요한 함수들이 몇번 호출 됐는지 등을 검사

Optional.of()

  • 반드시 값이 있어야 하는 객체인 경우
  • 해당 메서드 null 인 경우 -> NullPointException

userRepository.save()

-> 테스트하지 않음 why? 리턴값도 없고, 행위지정이 없기 때문에 실행 한척 만 함

테스트 코드 작성 순서

  1. when -> 테스트할 로직 작성
  2. given -> 테스트에 필요한 입력값 , 리턴값 등을 작성
  3. then -> 검증
profile
개발자꿈나무

0개의 댓글