[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 작성 (2)

2024. 12. 8. 18:26·Programming/etc

1. Java 단위 테스트 작성 준비

 

a. 필요한 라이브러리

 

Java 단위테스트 작성에는 크게 2가지 라이브러리가 사용됩니다.

  • JUnit5 : 자바 단위 테스트를 위한 테스팅 프레임워크
  • AssertJ : 자바 테스트를 돕기 위해 다양한 문법을 지원하는 라이브러리

JUnit만으로도 단위 테스트를 충분히 작성할 수 있습니다. 하지만 JUint에서 제공하는 assertEquals()와 같은 메서드는 AssertJ가 주는 메서드에 비해 가독성이 떨어집니다. 그렇게 때문에 순수 Java 애플리케이션에서 단위 테스트를 위해 JUnit5와 AssertJ 조합이 많이 사용됩니다.

 

💡 AssertJ가 가독성이 좋은 이유

1. JUnit의 assertEquals()
@Test
void testAddition() {
	int result = 3 + 5;
	assertEquals(8, result);
}

- 기댓값(expected)과 실제값(actual)의 순서가 헷갈릴 수 있습니다.
-> assertEquals(expected, actual) 순서이지만, 실수로 반대로 작성할 가능성이 있습니다.

- 자연어처럼 읽히지 않습니다.
-> assertEquals(8, result) → “8이 결과와 같다”보다 “결과가 8이어야 한다”가 더 자연스럽게 읽힘


2. AssertJ의 assertThat()
@Test
void testAddition() {
	int result = 3 + 5;
	assertThat(result).isEqualTo(8);
}​

- 자연스럽게 읽힙니다.
-> "결과가 8이어야 한다"처럼 직관적입니다,

- 메서드 체이닝이 가능하여 다양한 조건을 쉽게 추가 가능합니다.
assertThat(result)
    .isNotNull()
    .isGreaterThan(5)
    .isLessThan(10);​

 

b. given/when/then 패턴

 

given-when-then 패턴이란 1개의 단위 테스트를 3가지 단계로 나누어 처리하는 패턴으로, 각각의 단계는 다음을 의미합니다.

  • given(준비) : 어떠한 데이터가 준비되었을 때
  • when(실행) : 어떠한 함수를 실행하면
  • then(검증) : 어떠한 결과가 나와야 한다.

 

c. 테스트 코드 작성 공통 규칙

@Test
@DisplayName("유저 정보 조회 성공")
void getUserInfo_Success() {
    // given

    // when

    // then
}

@Test는 해당 메서드가 단위 테스트임을 명시하는 어노테이션입니다. JUnit은 테스트 패키지 하위의 @Test 어노테이션이 붙은 메서드를 단위 테스트로 인식하여 실행시킵니다. 이 상태로 실행하면 테스트 이름이 함수 이름으로 default로 지정되는데, 위의 코드에서는 @DisplayName 어노테이션을 사용하여 읽기 좋은 다른 이름을 부여했습니다.

 

또한, 테스트 코드는 앞서 설명한 given-when-then 구조로 흔히 작성되는데, 단위 테스트 내에 주석으로 이 단계를 명시해 주면 읽기 좋은 테스트 코드를 작성할 수 있습니다.

 

2. Mockito

 

a. Mockito란

 

Mockito는 개발자가 동작을 직접 제어할 수 있는 가짜 객체를 지원하는 테스트 라이브러리입니다. 일반적으로 Spring으로 웹 애플리케이션을 개발하면, 여러 객체들의 의존성이 생깁니다. 이러한 의존성은 단위 테스트를 작성하는데 어렵게 하기 때문에 이를 해결하기 위해 가짜 객체를 주입시켜 주는 Mockito 라이브러리를 활용할 수 있습니다. Mockito를 활용하면 가짜 객체에 원하는 결과를 Stub 하여 단위 테스트를 진행할 수 있습니다. 물론 라이브러리 도구가 필요없다면 사용하지 않는 것이 가장 좋다고 생각합니다.

 

 

b. Mockito 작성법

 

b-1. Mockito 객체 의존성 주입

 

Mockito에서 가짜 객체의 의존성 주입을 위해서는 크게 3가지 어노테이션이 사용됩니다.

  • @Mock : 가짜 객체를 만들어 반환해주는 어노테이션
  • @Spy : Stub하지 않은 메서드들은 원본 메서드 그대로 사용하는 어노테이션
  • @InjectMocks : @Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입시켜주는 어노테이션

예를 들어 UserController에 대한 단위 테스트를 작성하고자 할 때, UserService를 사용하고 있다면 @Mock 어노테이션을 통해 가짜 UserService를 만들고, @InjectMocks를 통해 UserController에 이를 주입시킬 수 있습니다.

 

 

b-2. Stub로 결과 처리

 

앞서 설명하였듯 의존성이 있는 객체는 가짜 객체를 주입하여 어떤 결과를 반환하라고 정해진 답변을 준비시켜야 합니다. Mockito에서는 다음과 같은 stub 메서드를 제공합니다.

  • doReturn() : 가짜 객체가 특정한 값을 반환해야 하는 경우
  • doNothing() : 가짜 객체가 아무 것도 반환하지 않는 경우
  • doThrow() : 가짜 객체가 예외를 발생시키는 경우

 

b-3. Mockito와 JUnit의 결합

 

Mockito도 테스팅 라이브러리이기 때문에 JUnit과 결합되기 위해서는 별도의 작업이 필요합니다. 기존의 JUnit4에서 Mockito를 활용하기 위해서는 클래스 어노테이션으로 @RunWith(MockitoJUnitRunner.class)를 붙여주어야 연동이 가능했습니다. 하지만 SpringBoot 2.2.0부터 공식적으로 JUnit5를 지원함에 따라, 이제부터는 @ExtendWith(MockitoExtension.class)를 사용해야 결합이 가능합니다.

 

3. 단위 테스트 작성 예시

@Transactional(readOnly = true)
public UserInfoResponse getUserInfo(Long userId) {
	User user = getUserById(userId);
	Reward reward = rewardRepository.findById(userId)
		.orElseThrow(() -> new ResourceNotFoundException(ErrorCode.REWARD_NOT_FOUND));
	
    return new UserInfoResponse(user.getNickname(), reward.getExperience(), reward.getNutrition());
}

위와 같은 유저 조회 코드에 대한 테스트 코드를 작성해보겠습니다.

 

a. 단위 테스트 작성 준비

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

앞서 설명하였듯 JUnit5와 Mockito를 연동하기 위해서는 @ExtendWith(MockitoExtension.class)를 사용해야 합니다. 이를 클래스 어노테이션으로 붙여 다음과 같이 테스트 클래스를 작성할 수 있습니다.

 

 

b. 유저 조회 Test

@BeforeEach
void setUp() {
	testUser = User.builder()
  		.email(USER_EMAIL)
		.nickname(USER_NICKNAME)
		.provider(AuthProvider.KAKAO)
		.profileImage("profile.jpg")
			.build();
	testUser.setId(USER_ID);

	testReward = Reward.builder()
		.user(testUser)
		.experience(0)
		.nutrition(0)
		.build();
}

우선 유저를 조회하기 위해서는 유저 객체가 필요합니다. 그렇기 때문에 테스트 시작 전에 @BeforeEach를 사용하여 testUser와 testReward 객체를 초기화하는 역할을 합니다.

  • testUser : User 객체를 빌더 패턴을 사용하여 생성하고, 이메일, 닉네임, 인증 제공자(Kakao), 프로필 이미지 등을 설정합니다. 이후 setId() 메서드로 사용자 ID를 설정합니다.
  • testReward : Reward 객체를 빌더 패턴을 사용하여 생성하고, testUser를 연결한 후 경험치와 영양값을 0으로 설정합니다.

 

@Test
@DisplayName("유저 정보 조회 성공")
void getUserInfo_Success() {
    // given
    when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser));
    when(rewardRepository.findById(USER_ID)).thenReturn(Optional.of(testReward));

    // when

    // then
}

given 단계에서는 Mockito를 사용하여 userRepository와 rewardRepository의 findById 메서드가 특정 값을 반환하도록 미리 설정합니다. userRepository에서 USER_ID로 데이터를 조회하면 testUser라는 사용자 정보를 반환하고, rewardRepository에서는 USER_ID로 데이터를 조회하면 testReward라는 보상 정보를 반환하도록 지정합니다.

 

@Test
@DisplayName("유저 정보 조회 성공")
void getUserInfo_Success() {
    // given
    when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser));
    when(rewardRepository.findById(USER_ID)).thenReturn(Optional.of(testReward));

    // when
    UserInfoResponse response = userService.getUserInfo(USER_ID);

    // then
}

when 단계에서는 userService.getUserInfo(USER_ID) 메서드를 호출하여, 이전에 설정한 userRepository와 rewardRepository에서 반환된 사용자 정보와 보상 정보를 기반으로 UserInfoResponse 객체를 받아옵니다.

 

@Test
@DisplayName("유저 정보 조회 성공")
void getUserInfo_Success() {
    // given
    when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser));
    when(rewardRepository.findById(USER_ID)).thenReturn(Optional.of(testReward));

    // when
    UserInfoResponse response = userService.getUserInfo(USER_ID);

    // then
    assertNotNull(response);
    assertEquals(testUser.getNickname(), response.nickname());
}

then 단계에서는 assertNotNull을 사용해 response 객체가 null이 아님을 확인합니다. 이는 userService.getUserInfo(USER_ID) 호출이 정상적으로 응답을 반환했는지를 검증하는 과정입니다. 또한, assertEquals()를 사용해 response.nickname()이 testUser.getNickname()과 일치하는지 비교합니다. 즉, 반환된 사용자 정보의 닉네임이 예상한 값과 정확히 일치하는지 확인하는 검증입니다.

'Programming > etc' 카테고리의 다른 글

[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 작성 (1)  (0) 2024.12.06
'Programming/etc' 카테고리의 다른 글
  • [Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 작성 (1)
chanyoungdev
chanyoungdev
chanyoungdev
  • chanyoungdev
    Young Code
    chanyoungdev
  • 전체
    오늘
    어제
    • 분류 전체보기 (28)
      • Programming (12)
        • Java (0)
        • Spring (10)
        • etc (2)
      • Data Infra (5)
        • Database (1)
        • Redis (2)
        • etc (2)
      • DevOps (4)
        • CI CD (1)
        • Nginx (2)
        • Docker (0)
        • Aws (0)
        • etc (1)
      • Algorithm (6)
      • etc (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • First Tech Blog
    • Github
  • 공지사항

  • 인기 글

  • 태그

    Redis
    domain
    nginx
    캐시
    elasticsearch
    Mockito
    단위 테스트
    lock
    공통 응답
    ssl
    Virtual Thread
    junit
    OAuth
    Algorithm
    JWT
    cache
    infra architecture
    Infra
    Spring Batch
    spring security
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
chanyoungdev
[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 작성 (2)
상단으로

티스토리툴바