[TIL | 내일배움캠프] 단위테스트 작성하기

변채주·2025년 11월 6일

Spring

목록 보기
7/13

앞으로 개발하면서 테스트 코드를 동시에 작성해나가는 능력이 필요할 듯하여 프로젝트가 끝난 오늘, 한번 만져보기로 했다.

가보자고~!

단위테스트

단위테스트는 잘게 쪼개려면 얼마든지 잘게 쪼갤 수 있다.
제약조건 검증, 단순 로직 동작 테스트...

특징 1) 빠른 실행속도

기존 프로젝트대로라면 DB도 갔다오고, 네트워크 통신도 봐야하는 과정이 있다. 단위테스트는 통신에 필요한 외부 요소들을 Mock(모의로 만드는 객체)를 활용해 테스트를 진행하기 때문에 본 프로젝트보다 상대적으로 빠르게 실행된다.

특징 2) 독립성

외부 요소가 없고 각 테스트마다 독립적으로 실행되므로 다른 테스트에 영향을 주지 않는다.

특징 3) 반복 가능

테스트 결과를 정해놓고 작성되기 때문에 언제 실행해도 동일한 결과가 나오도록 해야 한다.

특징 4) 명확한 실패 원인

특정 메서드의 문제를 즉시 파악할 수 있다.

JUnit5

: 자동화 테스트 도구

주요 기능

테스트 실행 시

  • @Test: 테스트 코드라는 걸 나타낸다.
  • @DisplayName: 테스트에 이름을 붙여준다.

테스트 수명 주기

  • @BeforeEach: 각 테스트 실행 전에 호출
  • @AfterEach: 각 테스트 실행 후에 호출
  • @BeforeAll: 모든 테스트 시작 전 1회 호출
  • @AfterAll: 모든 테스트 종료 후 1회 호출

Mockito

: 모의(Mocking) 프레임워크

주요 기능

  • @ExtendWith(MockitoExtension.class): Mockito 기능을 사용할 수 있게 해준다.(Mock 어노테이션 동작 활성화)
  • @Mock: 가짜 객체 생성
    (ex. @Mock ProductRepository)
  • @InjectMocks: @Mock으로 만든 객체들을 테스트 대상 클래스에 주입한다.
    (ex: @InjectMocks ProductService)
  • when(...).thenReturn(...): 특정 메서드가 호출될 때 원하는 값을 반환하도록 설정
  • verify(...): 특정 메서드가 예상대로 호출되었는지 확인
  • ArgumentCaptor: 메서드 호출 시 전달된 인자 값을 캡처해 검증
  • Spy: 실제 객체를 사용하되 특정 메서드만 스텁(가짜 동작 정의) 하거나 호출 검증을 하고 싶을 때 사용

이전 과제였던 LunchVote에서 UserService 로직 중 사용자 등록 성공에 대한 테스트코드를 작성해봤다.

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
    // 가짜 UserRepository 생성
    @Mock
    private UserRepository userRepository;
    //가짜 passwordEncoder 생성
    private PasswordEncoder passwordEncoder;
    @InjectMocks
    private UserService userService;

    @Test
    @DisplayName("사용자 등록 성공")
    void createUserSuccess() { //올바른 이메일 입력 -> 사용자 등록 성공 test
        //1. given
        CreateUserRequest request = new CreateUserRequest("test@example.com",
                "test1234",
                "tester");
        String encodedPassword = "encodedTest1234";

        //email 형식 검증
        when(userRepository.existsByEmail(request.getEmail())).thenReturn(false);
        //비밀번호 암호화
        when(passwordEncoder.encode(request.getPassword())).thenReturn(encodedPassword);

        User user = User.builder()
                .id(1L)
                .email(request.getEmail())
                .password(encodedPassword)
                .name(request.getName())
                .role(UserRoleEnum.USER)
                .build();

        //가짜 Repository의 save 메서드에
        //any(User.class) User 클래스 타입의 객체가 변수로 들어오고
        // when() 안의 내용이 실행되면 thenReturn() 안의 결과값이 나와야 한다.
        when(userRepository.save(any(User.class))).thenReturn(user);

        //2. when
        GetUserResponse response = userService.save(request);

        //3. then
        //올바르게 매핑되었는지 확인
        assertNotNull(response); //객체의 null 여부 확인
        assertEquals(request.getEmail(), response.getEmail()); // 이메일이 일치하는지 확인
        assertEquals(request.getName(), response.getName()); // 이름이 같은지 확인
        assertEquals(UserRoleEnum.USER, response.getRole()); // role 같은지 확인

        //동작 검증 verify
        //1.given에서 적었던 동작(when.thenReturn ~ )이 제대로 수행(호출)되었는지 확인
        verify(userRepository).existsByEmail(request.getEmail());
        verify(passwordEncoder).encode(request.getPassword());
        verify(userRepository).save(any(User.class));

    }
}

테스트 결과는....

😱 실패
원인은 내일 찾아서 수정해보겠다...

profile
우당탕탕얼레벌레 개발 일지

0개의 댓글