Controller, Service, Repository, Entity, DTO 레이어에 맞는 단위 테스트 작성하는 프로젝트에 대한 회고?

금은체리·2023년 12월 7일
1

Spring

목록 보기
49/49

새롭게 알게 된 사실

  1. @ActiveProfiles("test")
    • @Value값을 받아오기 위해 사용
    • test용 설정 정보를 받아오게 함
    • 실제 어플리케이션 뒤에 붙은 설정 파일을 가져옴
      • ex) application.properties
    • src/test/resources/application-test.properties
      • // DB 연결 정보 
        spring.datasource.url=jdbc:h2:mem:testdb
        spring.datasource.driver-class-name=org.h2.Driver
        spring.datasource.username=test
        spring.datasource.password=password
        spring.h2.console.enabled=true
        
        // test에서 사용할 secretkey 값
        jwt.secret.key=~~~

  1. assertNotNull(token);
    • JUnit에서 많이 사용되는 메서드
    • assert : 주장하다, 표명하다
      • 현재 상황에 대한 확실한 재확인 및 에러검출의 용도로 사용

  1. var
    • Java에서 지원하고 있는 변수값
    • var 사용하면 변수 선언할 때 타입 생략 가능

  1. claims
    • Payload 부분에는 토큰에 담을 정보가 들어있음
    • 여기에 담는 정보의 한 ‘조각’ 을 클레임(claim) 이라고 부름
    • name / value 의 한 쌍으로 이뤄짐
    • 토큰에는 여러개의 클레임 들을 넣을 수 있음

  1. @WebMvcTest(테스트 할 대상이 되는 컨트롤)

    • @WebMvcTest(TodoController.class)

    • 스프링MVC 웹 관련 설정들을 넣어주게 됨

    • 웹MVC 사용하려면 MockMvcBuilders 필수적으로 사용해줘야함

    • setUp() : setUp을 통해서 실제 유저 정보를 DB에 저장해줌

      • @BeforeEach
            void setUp() {
                mockMvc = MockMvcBuilders
                    .webAppContextSetup(context) // 기본적으로 context 정보를 Setup해줘야함
                    .build(); // 이까지는 필수로 넣어줘야 하는 부분
                    
                // 주입할 유저 정보를 만들어 주는 부분
                
                // Mock 테스트 UserDetails 생성
                UserDetailsImpl testUserDetails = new UserDetailsImpl(TEST_USER);
        
                // SecurityContext 에 인증된 사용자 설정
                SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
                    testUserDetails, testUserDetails.getPassword(), testUserDetails.getAuthorities()));
            }

  1. .builder
    • 사용하려면 @Builder 필요함
    • TodoRequestDTO TEST_TODO_REQUEST_DTO = TodoRequestDTO.builder()
                  .title(TEST_TODO_TITLE)
                  .content(TEST_TODO_CONTENT)
                  .build();
      • 위 코드에선 TodoRequestDTO에 @Builder 달아줘야함
      • 생성자를 통해 객체를 생성하는데 몇 가지 단점이 있어 객체를 생성하는 별도 builder를 두는 방법

  1. mockMvc.perform

    • perform 메서드를 통해 해당 메서드(빌더 패턴의 요청 정보)와 그 메서드에 해당하는 Url을 넣어줌

      • var action = mockMvc.perform(post("/api/todos")
    • 실제 웹 호출이 발생하는 것 처럼 동작하게 해줌

    • Pathparam도 가능!

    • var action = mockMvc.perform(get("/api/todos/{todoId}", TEST_TODO_ID)

    • // when
              var action = mockMvc.perform(post("/api/todos") // 2. perform(호출)하게되면 -> 3. 응답값을 확인할 수 있는 action이 변수로 나옴
                      .contentType(MediaType.APPLICATION_JSON)
                      .accept(MediaType.APPLICATION_JSON)  // 1. 얘를 요청 Body에 넣어서
                      .content(objectMapper.writeValueAsString(TEST_TODO_REQUEST_DTO)));
      • .contentType(MediaType.APPLICATION_JSON)
        • 요청의 콘텐츠 타입을 JSON으로 설정
        • 이 경우에는 "application/json"으로 설정됨
      • .accept(MediaType.APPLICATION_JSON)
        • 응답으로 받기를 원하는 미디어 타입을 설정
        • 여기서는 JSON을 받기를 원하므로 "application/json"으로 설정됨
      • .content(objectMapper.writeValueAsString(TEST_TODO_REQUEST_DTO))
        • 요청 본문에 전송할 데이터를 설정
        • TEST_TODO_REQUEST_DTO 객체를 JSON 문자열로 변환하여 설정
        • objectMapper는 객체를 JSON 문자열로 변환하기 위한 Jackson ObjectMapper

  1. action.andExpect()
// then
        action.andExpect(status().isCreated());
        verify(todoService, times(1)).createTodo(any(TodoRequestDTO.class), eq(TEST_USER));
  • .andExpect()를 통해 실제로 어떤 응답값을 받았는지 확인 가능
    • (status()값이 isCreated()인지 확인)
  • verify
    • 실제 어떤 메서드가 호출되었나 확인하는 용도로 사용
    • Mockito에서 지원
    • 안에 클래스는 Mock이 들어와야함
    • 두번째 변수로 몇 회 호출되었는지 확인을 위한 times 사용
    • 위 코드는 createTodo가 1번 호출되어야한다는 것을 의미
  • .createTodo(어떤 파라미터가 들어오는지에 대한 조건)
    • 조건이 매칭이 안되면 verify가 수행이 안되었다고 봄
    • eq(어떤 값이랑 같아야하는지) == equals
  • jsonPath
    • // then
                  action
                          .andExpect(status().isOk())
                          .andExpect(jsonPath("$.title").value(TEST_TODO_TITLE))
                          .andExpect(jsonPath("$.content").value(TEST_TODO_CONTENT));
    • ResultMatcher을 생성해주는 생성자
    • Json의 어떤 값을 볼지 express형태의 표현자를 넣음
    • $.title
      • $는 응답의 루트 경로를 뜻함
      • json안에 있는 title을 보겠다는 의미
    • .value(TEST_TODO_TITLE)
      • .value를 통해 어떤 값이랑 같은지 (변수)를 넣어주면 됨

  1. ReflectionTestUtils
ReflectionTestUtils.setField(newTodo, Todo.class, "id", id, Long.class);
  • .setField를 해주면
    • newTodo : 어떤 객체(필드)를 값을 넣어줄거고,
    • Todo.class : 걔는 어떤 클래스고,
    • "id" : 클래스에 어떤 필드를 넣어줄 것이며,
    • id : 그 값
    • Long.class 그 값의 타입
  • setter 메소드가 없어도 그 값을 설정해줄 수 있음
  • 테스트 용도로만 사용해야함!

  1. SerializationUtils
var newTodo = SerializationUtils.clone(todo);
  • 객체를 그대로 넣는게 아니라 복제하고 새로운 객체 만들어서 사용
    • 생성을 여러번해도 그 때 그때 다른 값이 나오기 때문
  • .clone은 아무거나 못하고 todo가 implements Serializable해야함
    • public class Todo implements Serializable

  1. jsonPath($[?(조건식)])
 // then
        action
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[?(@.user.username=='" + TEST_USER.getUsername() + "')].todoList[*].id")
                        .value(Matchers.containsInAnyOrder(testTodo1.getId().intValue(), testTodo2.getId().intValue())))
                .andExpect(jsonPath("$[?(@.user.username=='" + TEST_ANOTHER_USER.getUsername() + "')].todoList[*].id")
                        .value(Matchers.containsInAnyOrder(testAnotherTodo.getId().intValue())));
        verify(todoService, times(1)).getUserTodoMap();
  • .andExpect(jsonPath("$[?(@.user.username=='" + TEST_USER.getUsername() + "')].todoList[*].id")
    1. @
      • 실제 해당 배열에 있는 값들의 루트 = N번째
    2. .todoList[*]
      • 앞에 있는 조건문에서 값을 가지고 왔으면 거기에 todoList를 조회 함
      • [*] : todoList에 있는 모든 것을 조회 하겠다라는 의미
    3. .id
      • todoList의 모든 것을 조회하지만, 그 중에서 id만 조회하겠다는 의미
  • .value(Matchers.containsInAnyOrder(testTodo1.getId().intValue(), testTodo2.getId().intValue())))
    1. .value
      • value메서드를 통해 값을 비교
    2. Matchers.containsInAnyOrder(내가 원하는 값)
      • 순서상관없이 내가 원하는 값들을 value가 매칭되는지 확인해줌
        • 원하는 값이 포함되어 있는지 확인해줌

  1. @ExtendWith(MockitoExtension.class)
  • Mockito를 JUnit 5 테스트에서 사용할 수 있게 되며,
  • Mockito의 목(mock) 객체를 생성하고 관련된 설정을 자동으로 수행할 수 있게 됨

  1. @InjectMocks
@InjectMocks
    TodoService todoService;
  • TodoService 클래스에 내가 @Mock으로 선언한 애들을 주입하겠다는 의미
  • 테스트 대상에 많이 사용

  1. @EqualsAndHashCode(callSuper = false)
  • 필드값이 같은지 확인해줌
  • (callSuper = false)
    • 상속받고있는 Super클래스에 대한 것도 할거냐?
  • Equals에다가 내가 정의한 HashCode를 기준으로 같은지 여부를 확인하겠다는 의미
  • 이 애너테이션을 통해서 HashCode가 자동으로 생성됨
    • 그 HashCode는 필드값들을 변환한 것
      • 필드값만 같으면 같은 HashCode가 나옴

  1. @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
  • 실제 데이터베이스를 테스트용으로 사용하겠다는 의미
profile
전 체리 알러지가 있어요!

0개의 댓글