테스트 코드 작성하기

이민호·2024년 6월 18일
0

❗필수 구현 기능

  • 🆕 AOP 추가하기
    • 모든 API(Controller)가 호출될 때, Request 정보(Request URL, HTTP Method)를
      @Slf4J Logback 라이브러리를 활용하여 Log로 출력해주세요.
    • 컨트롤러 마다 로그를 출력하는 코드를 추가하는것이 아닌, AOP로 구현해야만 합니다.
@Pointcut("execution(* com.prac.music.domain.board.controller.*.*(..))")
private void board() {}

@Pointcut("execution(* com.prac.music.domain.comment.controller.*.*(..))")
private void comment() {}

@Pointcut("execution(* com.prac.music.domain.like.controller.*.*(..))")
private void like() {}

@Pointcut("execution(* com.prac.music.domain.mail.controller.*.*(..))")
private void mail() {}

@Pointcut("execution(* com.prac.music.domain.user.controller.*.*(..))")
private void user() {}

@Around("board() || comment() || like() || mail() || user()" )
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info(joinPoint.getSignature().toShortString() + " start");

        try {

            return joinPoint.proceed();

        } finally {

            log.info(joinPoint.getSignature().toShortString() + " end");

        }
    }

  • api가 실행될때, 끝날때 api의 이름을 출력하는 log
    • domain 방식으로 설계된 프로젝트여서 각각의 controller를 지정하는 @Pointcut이 많은 것이 아닌가라는 생각이 든다.
  • 🆕 DTO, Entity Test 추가하기
    • @Test 를 사용해서 DTO 와 Entity Test 를 추가합니다.
    • User, Post, Comment, DTO 에 존재하는 메서드들에 대해서 “단위 테스트” 를 추가합니다.
    • 특정 상황에 예외가 정상적으로 발생하고 있는지도 테스트 합니다.
@Test
    @DisplayName("User Entity Test")
    void test1() throws IOException {
        // given
        SignupRequestDto requestDto = new SignupRequestDto(
                "testId1",
                "testPassword1!",
                "testName",
                "test@email.com",
                "test Introduce"
        );

        MultipartFile file = null;

        // when
        User user = userService.createUser(requestDto, file);

        // then
        assertEquals(requestDto.getUserId(), user.getUserId());
        assertTrue(passwordEncoder.matches(requestDto.getPassword(), user.getPassword()));
        assertEquals(requestDto.getName(), user.getName());
        assertEquals(requestDto.getEmail(), user.getEmail());
        assertEquals(requestDto.getIntro(), user.getIntro());
    }
  • given 에 입력값, when에 생성 메서드(Post api)를 동작시켜 비교시켜 테스트

    • 게시글과 댓글은 종속된 엔티티가 테스트 코드 내에서 제대로 존재하지 않아서 통과하지 못하는 것으로 보인다.
  • 🆕 Controller Test 추가하기
    • @WebMvcTest 를 사용하여 Controller Test 를 추가합니다.
    • Post, Comment Controller 에 대해서 테스트를 추가합니다.
    • 특정 상황에 예외가 정상적으로 발생하고 있는지도 테스트 합니다.


🛠️ 오류해결

  • ✨ JpaAuditing 관련 어노테이션 부착 후 해결
@WebMvcTest(
        controllers = UserController.class,
        excludeFilters = {
                @ComponentScan.Filter(
                        type = FilterType.ASSIGNABLE_TYPE,
                        classes = SecurityConfiguration.class
                )
        }
)
@MockBean(JpaMetamodelMappingContext.class)
class UserControllerTest {
    private MockMvc mvc;
    private Principal mockPrincipal;

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private ObjectMapper objectMapper;

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @MockBean
    private UserService userService;

    private JwtService jwtService = new JwtService();

    @BeforeEach
    public void setup() {
        mvc = MockMvcBuilders.webAppContextSetup(context)
                .apply(springSecurity(new MockSpringSecurityFilter()))
                .build();

    }

    @Test
    @DisplayName("Signup Request")
    void test1() throws Exception {

        //given
        SignupRequestDto requestDto = SignupRequestDto.builder()
                .userId("testId1")
                .password("testPassword1!")
                .name("testName")
                .email("test@email.com")
                .intro("test Introduce")
                .build();

        MockMultipartFile file = new MockMultipartFile(
                "file",
                "profileImage.png",
                "multipart/form-data",
                "some image content".getBytes()
        );

        MockMultipartFile user = new MockMultipartFile(
                "user",
                "",
                "application/json",
                objectMapper.writeValueAsBytes(requestDto)
        );

        // when - then
        mvc.perform(multipart("/api/users/signup")
                        .file(user)
                        .file(file))
                .andExpect(status().isOk())
                .andDo(print());
    }

    @Test
    @DisplayName("Login Request")
    public void test2() throws Exception {

        //given
        LoginRequestDto requestDto = LoginRequestDto.builder()
                .userId("testId1")
                .password("testPassword1!")
                .build();

        String jsonRequest = objectMapper.writeValueAsString(requestDto);

        //when - then
        mvc.perform(post("/api/users/login")
                        .contentType("application/json")
                        .content(jsonRequest))
                .andExpect(status().isOk())
                .andDo(print());
    }

    @Test
    @DisplayName("Logout Request")
    public void test3() throws Exception {

        // given
        User user = User.builder()
                .userId("testId1")
                .build();
        UserDetailsImpl userDetails = new UserDetailsImpl(user);
        mockPrincipal = new UsernamePasswordAuthenticationToken(userDetails, "");

        // when
        userService.logoutUser(userDetails.getUser());

        // then
        mvc.perform(put("/api/users/logout")
                        .principal(mockPrincipal))
                .andExpect(status().isOk())
                .andDo(print());
    }

    @Test
    @DisplayName("Signout Request")
    public void test4() throws Exception {

        // given
        User user = User.builder()
                .id(1L)
                .userId("testId1")
                .password("testPassword1!")
                .name("testName")
                .email("test@email.com")
                .intro("test Introduce")
                .status(UserStatusEnum.NORMAL)
                .refreshToken("fidsbafilubasdjiklfboia")
                .profileImage(null)
                .build();

        SignoutRequestDto requestDto = SignoutRequestDto.builder()
                .password("testPassword1!")
                .build();

        UserDetailsImpl userDetails = new UserDetailsImpl(user);
        mockPrincipal = new UsernamePasswordAuthenticationToken(userDetails, "");
        // when

        // then
        mvc.perform(put("/api/users/signout")
                        .principal(mockPrincipal)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(requestDto)))
                .andExpect(status().isOk())
                .andDo(print());
    }


}

JpaAuditing 관련 오류 외에도 기존 프로젝트에서 SignOut api 가 실행이 안되는 500코드 오류 발생

  • 기존 requestDto의 필드가 하나 밖에 없어서 생기는 오류였음
    -> @Jacksonized 어노테이션으로 해결
  • 🆕 Service Test 추가하기
    • @ExtendWith 를 사용하여 Service Test 를 추가합니다.
    • User, UserDetails, Post, Comment Service 에 대해서 “통합 테스트” 를 추가합니다.
    • 단순 DB CRUD 와 별개로 코드 레벨에서의 비즈니스 로직에 대한 테스트가 필요한 경우라면 “단위 테스트”를 추가합니다.
      • ex) 비밀번호가 암호화 되었는가
    • 특정 상황에 예외가 정상적으로 발생하고 있는지도 테스트 합니다.
package com.prac.music.service;

import com.prac.music.domain.board.dto.BoardRequestDto;
import com.prac.music.domain.board.dto.BoardResponseDto;
import com.prac.music.domain.board.service.BoardService;
import com.prac.music.domain.user.entity.User;
import com.prac.music.domain.user.entity.UserStatusEnum;
import com.prac.music.domain.user.service.JwtService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

import static org.hibernate.validator.internal.util.Contracts.assertNotNull;

@ExtendWith(MockitoExtension.class) // @Mock 사용을 위해 설정합니다.
public class BoardServiceTest {
    
    @Mock
    JwtService jwtService;

    @InjectMocks
    BoardService boardService;

    User user;

    @BeforeEach
    public void setUp() {
        user = User.builder()
                .id(1L)
                .userId("testId1")
                .password("testPassword1!")
                .name("testName")
                .email("test@email.com")
                .intro("test Introduce")
                .status(UserStatusEnum.NORMAL)
                .refreshToken(jwtService.createRefreshToken("testId1"))
                .profileImage(null)
                .build();

    }

    @Test
    @DisplayName("createBoard Success")
    void test1() throws IOException {
        //given
        BoardRequestDto requestDto = BoardRequestDto.builder()
                .title("test title")
                .contents("test contents")
                .build();
        List<MultipartFile> files = List.of();
        //when
        BoardResponseDto createdBoard = boardService.createBoard(requestDto, user, files);

        //then
        assertNotNull(createdBoard);
    }
}

@Setup 어노테이션으로 기존재해야할 엔티티를 미리 작성하고 테스트를 진행하는 방식으로 작성했다.
그런데 작성하고 보니 기존 Entity Test에서 진행했던 방식과 동일한 것으로 여겨져서 Entity Test를 더 단순하게 생각했어야 하는 구나 하고 생각했다.

profile
둘뺌

0개의 댓글

관련 채용 정보