10.5 테스트 코드 작성

SummerToday·2024년 3월 25일
1
post-thumbnail
post-custom-banner

테스트 코드 재작성

기존에 작성한 BlogApiContorollerTest의 테스트 코드에 10장 OAuth2 인증 관련 로직을 추가 구현한 부분을 고려하여 테스트 코드를 재작성한다.

// test - controller - BlogApiControllerTest.java

@SpringBootTest
@AutoConfigureMockMvc
class BlogApiControllerTest {

      ~ 생략 ~ 

    @Autowired
    UserRepository userRepository;  // 추가

    User user;

      ~ 생략 ~

    @BeforeEach
    void setSecurityContext() {   // setSecurityContext() 추가
        userRepository.deleteAll();
        user = userRepository.save(User.builder()
                .email("user@gmail.com")
                .password("test")
                .build());

        SecurityContext context = SecurityContextHolder.getContext();
        context.setAuthentication(new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()));
    }


    @DisplayName("addArticle: 아티클 추가에 성공한다.")
    @Test
    public void addArticle() throws Exception {
        // given
        final String url = "/api/articles";
        final String title = "title";
        final String content = "content";
        final AddArticleRequest userRequest = new AddArticleRequest(title, content);

        final String requestBody = objectMapper.writeValueAsString(userRequest);

        Principal principal = Mockito.mock(Principal.class);  // 추가 
        Mockito.when(principal.getName()).thenReturn("username");  // 추가

        // when
        ResultActions result = mockMvc.perform(post(url)
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .principal(principal)  // 추가
                .content(requestBody));

          ~ 생략 ~
        
    }

    @DisplayName("findAllArticles: 아티클 목록 조회에 성공한다.")
    @Test
    public void findAllArticles() throws Exception {
        // given
        final String url = "/api/articles";
        Article savedArticle = createDefaultArticle();  // 추가

        // when
        final ResultActions resultActions = mockMvc.perform(get(url)
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultActions
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].content").value(savedArticle.getContent()))  // 추가
                .andExpect(jsonPath("$[0].title").value(savedArticle.getTitle()));  // 추가
    }

    @DisplayName("findArticle: 아티클 단건 조회에 성공한다.")
    @Test
    public void findArticle() throws Exception {
        // given
        final String url = "/api/articles/{id}";
        Article savedArticle = createDefaultArticle();  // 추가

        // when
        final ResultActions resultActions = mockMvc.perform(get(url, savedArticle.getId()));

        // then
        resultActions
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.content").value(savedArticle.getContent()))
                .andExpect(jsonPath("$.title").value(savedArticle.getTitle()));
    }


    @DisplayName("deleteArticle: 아티클 삭제에 성공한다.")
    @Test
    public void deleteArticle() throws Exception {
        // given
        final String url = "/api/articles/{id}";
        Article savedArticle = createDefaultArticle();

        // when
        mockMvc.perform(delete(url, savedArticle.getId()))
                .andExpect(status().isOk());

        // then
        List<Article> articles = blogRepository.findAll();

        assertThat(articles).isEmpty();
    }


    @DisplayName("updateArticle: 아티클 수정에 성공한다.")
    @Test
    public void updateArticle() throws Exception {
        // given
        final String url = "/api/articles/{id}";
        Article savedArticle = createDefaultArticle();  // 추가

        ~ 생략 ~ 
    }

    private Article createDefaultArticle() {  // createDefaultArticle() 추가
        return blogRepository.save(Article.builder()
                .title("title")
                .author(user.getUsername())
                .content("content")
                .build());
    }
}
  • setSecurityContext()

    • userRepository.deleteAll()
      테스트용으로 사용되는 userRepository에서 모든 사용자를 삭제한다. 이렇게 함으로써 테스트 중에 생성된 임의의 사용자 데이터가 다음 테스트에 영향을 주지 않도록 한다.

    • user = userRepository.save(User.builder()...)
      새로운 사용자를 생성하고 userRepository를 통해 저장한다. 해당 사용자는 이후에 테스트에서 인증에 사용된다.

    • SecurityContext context = SecurityContextHolder.getContext()
      Spring Security의 SecurityContext를 가져온다.

    • context.setAuthentication(new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()))
      SecurityContext에 사용자의 인증 정보를 설정한다. 이를 통해 테스트 코드에서는 특정 사용자로 인증된 것처럼 동작할 수 있다.


  • 글을 생성하는 API에서는 파라미터로 principal 객체를 받고 있는데 이 객체에 테스트 유저가 들어가도록 모킹한다. 해당 테스트 코드에서는 Principal 객체를 모킹해서 스프링 부트 애플리키에션에서 getName() 메서드를 호출하면 "userName"이라는 값을 반환한다.

    "모킹(Mocking)"은 테스트에서 실제 객체를 대신하여 행동을 정의하거나 시뮬레이션하는 것을 의미한다.

    MockMvc는 Spring MVC 컨트롤러를 테스트하는 데에 사용되는 반면, Mockito는 객체의 동작을 모의하고 테스트 중에 확인하는 데 사용된다. 두 도구는 서로 보완적으로 사용될 수 있으며, MockMvc를 사용하여 컨트롤러의 행동을 테스트하고, Mockito를 사용하여 컨트롤러가 의존하는 서비스나 데이터 접근 객체 등의 외부 의존성을 모의하여 테스트할 수 있다.

    Principal은 Java에서 보안 및 인증 관련 작업에 사용되는 인터페이스이다. 주로 웹 애플리케이션에서 현재 사용자의 정보를 나타내는 데에 사용된다. Spring Security와 같은 보안 프레임워크에서는 사용자가 인증(Authentication)되면 Principal 객체가 생성되고, 현재 사용자를 나타내는 역할을 한다. 보통 컨트롤러나 서비스 계층에서 Principal 객체를 사용하여 현재 사용자의 정보를 가져와서 작업을 수행한다.
    다음 주요 메서드들이 존재한다.

    • getName(): 현재 사용자의 이름을 반환한다. 주로 사용자의 식별자(ID)나 사용자명을 반환한다.
    • toString(): Principal 객체의 문자열 표현을 반환한다. 보통은 사용자의 정보를 문자열로 표현하여 반환한다.

    Mockito.when(mockedObject.methodCall()).thenReturn(expectedValue);

    • mockedObject: 모의 객체(mock object)이다. 해당 객체는 실제 객체의 역할을 대신하며, Mockito가 생성한 가짜 객체이다.
    • methodCall(): 모의 객체(mockedObject)의 특정 메서드 호출을 나타낸다.
    • thenReturn(expectedValue): methodCall()이 호출될 때 반환할 값을 지정한다. 해당 값은 메서드의 반환값이 된다.

  • createDefaultArticle()
    createDefaultArticle() 메서드는 테스트에서 사용할 기본적인 Article 객체를 생성하고 저장하는 메서드이다. 글을 작성하는 코드의 중복 작성을 막기 위해 따로 메서드화 한다.

    • Article.builder()를 사용하여 Article 객체를 생성한다.

    • 제목(title), 작성자(author), 내용(content)을 설정한다. 여기서는 작성자는 현재 사용자(user)의 이름을 사용한다.

    • blogRepository.save()를 사용하여 생성된 Article 객체를 저장한다. 해당 Article이 데이터베이스에 영구적으로 저장된다.




해당 글은 다음 도서의 내용을 정리하고 참고한 글임을 밝힙니다.
신선영, ⌜스프링 부트 3 벡엔드 개발자 되기 - 자바 편⌟, 골든래빗(주), 2023, 384쪽
profile
IT, 개발 관련 정보들을 기록하는 장소입니다.
post-custom-banner

0개의 댓글