JUnit 5 & Mockito 정리
JUnit 5 애노테이션과 Assertion부터 Mockito의 스터빙·호출 검증·ArgumentCaptor, 테스트 더블 다섯 종류까지 실제로 쓰는 문법만 예제로 모아둔 장문의 레퍼런스다.
작성날짜: 25.08.17
1. JUnit 5 기본 문법
1.1 기본 애노테이션
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class) // Mockito 확장 활성화
@DisplayName("클래스 설명")
class MyTest {
@BeforeAll
static void beforeAll() {
// 모든 테스트 실행 전 1번만 실행 (static 메서드)
}
@BeforeEach
void beforeEach() {
// 각 테스트 실행 전마다 실행
}
@Test
@DisplayName("테스트 설명")
void testMethod() {
// 테스트 로직
}
@AfterEach
void afterEach() {
// 각 테스트 실행 후마다 실행
}
@AfterAll
static void afterAll() {
// 모든 테스트 실행 후 1번만 실행 (static 메서드)
}
}
1.2 테스트 그룹화 (@Nested)
@Nested
@DisplayName("사용자 생성 테스트")
class UserCreationTest {
@Nested
@DisplayName("유효한 입력값")
class ValidInput {
@Test
void shouldCreateUserWithValidName() {}
}
@Nested
@DisplayName("유효하지 않은 입력값")
class InvalidInput {
@Test
void shouldThrowExceptionWithNullName() {}
}
}
1.3 조건부 테스트
@Test
@EnabledOnOs(OS.LINUX) // Linux에서만 실행
void onlyOnLinux() {}
@Test
@DisabledOnOs(OS.WINDOWS) // Windows에서는 실행 안함
void notOnWindows() {}
@Test
@EnabledOnJre(JRE.JAVA_11) // Java 11에서만 실행
void onlyOnJava11() {}
@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void only64Bit() {}
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "local")
void onlyInLocalEnv() {}
1.4 반복 테스트
@RepeatedTest(5)
@DisplayName("5번 반복 테스트")
void repeatedTest(RepetitionInfo repetitionInfo) {
System.out.println("실행 횟수: " + repetitionInfo.getCurrentRepetition());
}
@ParameterizedTest
@ValueSource(strings = {"apple", "banana", "cherry"})
void testWithValueSource(String fruit) {
assertNotNull(fruit);
}
@ParameterizedTest
@CsvSource({
"1, apple",
"2, banana",
"3, cherry"
})
void testWithCsvSource(int id, String name) {
assertTrue(id > 0);
assertNotNull(name);
}
@ParameterizedTest
@MethodSource("stringProvider")
void testWithMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana");
}
1.5 테스트 비활성화
@Test
@Disabled("아직 구현되지 않음")
void disabledTest() {}
@Test
@DisabledIf("java.time.LocalDate.now().getDayOfWeek().toString().equals('SUNDAY')")
void notOnSunday() {}
2. JUnit 5 Assertions
2.1 기본 Assertions
import static org.junit.jupiter.api.Assertions.*;
@Test
void basicAssertions() {
// 기본 검증
assertTrue(condition);
assertFalse(condition);
assertNull(object);
assertNotNull(object);
// 값 비교
assertEquals(expected, actual);
assertEquals(expected, actual, "실패 시 메시지");
assertNotEquals(unexpected, actual);
// 참조 비교
assertSame(expected, actual); // 같은 객체 참조
assertNotSame(unexpected, actual); // 다른 객체 참조
// 배열/컬렉션
assertArrayEquals(expectedArray, actualArray);
assertIterableEquals(expectedList, actualList);
// 문자열
assertEquals("Hello", actual);
assertTrue(actual.contains("substring"));
assertTrue(actual.startsWith("prefix"));
// 숫자 (부동소수점)
assertEquals(3.14, actual, 0.01); // delta 허용 오차
// 타입 검증
assertInstanceOf(String.class, object);
}
2.2 예외 Assertions
@Test
void exceptionAssertions() {
// 예외 발생 검증
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("잘못된 인수");
});
// 예외 발생하지 않음 검증
assertDoesNotThrow(() -> {
// 정상 실행되어야 하는 코드
});
// 예외 객체 자세히 검증
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("특정 메시지");
});
assertEquals("특정 메시지", exception.getMessage());
// 특정 시간 내 완료 검증
assertTimeout(Duration.ofSeconds(2), () -> {
// 2초 안에 완료되어야 하는 코드
Thread.sleep(1000);
});
// 시간 초과 시 즉시 중단
assertTimeoutPreemptively(Duration.ofSeconds(2), () -> {
Thread.sleep(1000);
});
}
2.3 그룹 Assertions
@Test
void groupedAssertions() {
// 모든 assertion을 실행하고 실패한 것들을 모두 보고
assertAll("사용자 정보 검증",
() -> assertEquals("John", user.getFirstName()),
() -> assertEquals("Doe", user.getLastName()),
() -> assertEquals(30, user.getAge())
);
}
2.4 커스텀 메시지
@Test
void customMessages() {
// 문자열 메시지
assertEquals(expected, actual, "값이 일치하지 않습니다");
// Supplier로 지연 계산 (실패할 때만 계산)
assertEquals(expected, actual, () -> "복잡한 계산: " + expensiveCalculation());
}
3. Mockito 기본 문법
3.1 Mock 생성
import org.mockito.*;
import static org.mockito.Mockito.*;
import static org.mockito.BDDMockito.*;
@ExtendWith(MockitoExtension.class)
class MockTest {
// 1. 애노테이션 방식
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@InjectMocks // Mock들이 주입될 대상
private UserService userService;
// 2. 프로그래밍 방식
@Test
void programmaticMockCreation() {
UserRepository mockRepo = mock(UserRepository.class);
EmailService mockEmail = mock(EmailService.class);
// 또는 타입 안전한 방식
UserRepository mockRepo2 = Mockito.mock(UserRepository.class);
}
}
3.2 Spy 생성
@Spy
private List<String> spyList = new ArrayList<>();
@Test
void spyExample() {
// 실제 객체의 일부 메서드만 Mock
List<String> list = new ArrayList<>();
List<String> spyList = spy(list);
// 실제 메서드 호출
spyList.add("one");
assertEquals(1, spyList.size()); // 실제 동작
// 특정 메서드만 Mock
when(spyList.size()).thenReturn(100);
assertEquals(100, spyList.size()); // Mocked 결과
}
4. Stubbing (when-then 패턴)
4.1 기본 Stubbing
@Test
void basicStubbing() {
// 기본 when-then
when(userRepository.findById(1L)).thenReturn(user);
when(userRepository.findById(2L)).thenReturn(null);
// BDD 스타일 (given-will)
given(userRepository.findById(1L)).willReturn(user);
// 메서드 체이닝
when(userRepository.findById(anyLong()))
.thenReturn(user1)
.thenReturn(user2)
.thenThrow(new RuntimeException());
}
4.2 다양한 반환 패턴
@Test
void returnPatterns() {
// 단순 값 반환
when(userRepository.count()).thenReturn(10L);
// 여러 값 순차 반환
when(userRepository.findAll())
.thenReturn(Arrays.asList(user1))
.thenReturn(Arrays.asList(user1, user2))
.thenReturn(Collections.emptyList());
// 예외 발생
when(userRepository.findById(-1L))
.thenThrow(new IllegalArgumentException("Invalid ID"));
// 여러 예외 타입
when(userRepository.save(any()))
.thenThrow(IllegalArgumentException.class)
.thenThrow(new RuntimeException("DB Error"));
// 콜백 함수 사용
when(userRepository.findById(anyLong())).thenAnswer(invocation -> {
Long id = invocation.getArgument(0);
return id > 0 ? new User(id, "User" + id) : null;
});
// 실제 메서드 호출
when(userRepository.findById(anyLong())).thenCallRealMethod();
// void 메서드에 예외 발생
doThrow(new RuntimeException()).when(emailService).sendEmail(any());
// void 메서드 아무것도 안함
doNothing().when(emailService).sendEmail(any());
}
4.3 Argument Matchers
@Test
void argumentMatchers() {
// 기본 matchers
when(userRepository.findById(any())).thenReturn(user);
when(userRepository.findById(anyLong())).thenReturn(user);
when(userRepository.findById(eq(1L))).thenReturn(user);
// 타입별 matchers
when(service.process(anyString())).thenReturn("result");
when(service.calculate(anyInt())).thenReturn(100);
when(service.isValid(anyBoolean())).thenReturn(true);
when(service.findByEmail(anyCollection())).thenReturn(users);
when(service.findByIds(anyList())).thenReturn(users);
when(service.findByMap(anyMap())).thenReturn(user);
// 조건부 matchers
when(userRepository.findById(argThat(id -> id > 0))).thenReturn(user);
when(userRepository.findByAge(intThat(age -> age >= 18))).thenReturn(users);
// 문자열 matchers
when(userRepository.findByName(startsWith("John"))).thenReturn(user);
when(userRepository.findByName(endsWith("Doe"))).thenReturn(user);
when(userRepository.findByName(contains("oh"))).thenReturn(user);
when(userRepository.findByName(matches("J.*n"))).thenReturn(user);
// null 체크
when(userRepository.findByEmail(isNull())).thenReturn(null);
when(userRepository.findByEmail(isNotNull())).thenReturn(user);
// varargs
when(service.concatenate(any(), any(), any())).thenReturn("result");
}
5. Verification (검증)
5.1 기본 Verification
@Test
void basicVerification() {
// 서비스 호출
userService.createUser("John", "john@email.com");
// 메서드 호출 검증
verify(userRepository).save(any(User.class));
verify(emailService).sendWelcomeEmail("john@email.com");
// 호출 횟수 검증
verify(userRepository, times(1)).save(any());
verify(userRepository, never()).delete(any());
verify(userRepository, atLeastOnce()).save(any());
verify(userRepository, atMost(3)).save(any());
verify(userRepository, atLeast(1)).save(any());
// 정확한 인수로 호출되었는지 검증
verify(userRepository).save(argThat(user ->
"John".equals(user.getName()) && "john@email.com".equals(user.getEmail())
));
}
5.2 호출 순서 검증 (InOrder)
@Test
void verificationOrder() {
// 서비스 호출
userService.createUser("John", "john@email.com");
// 순서 검증
InOrder inOrder = inOrder(userRepository, emailService);
inOrder.verify(userRepository).save(any(User.class));
inOrder.verify(emailService).sendWelcomeEmail("john@email.com");
}
5.3 추가 검증
@Test
void additionalVerifications() {
// 서비스 호출
userService.findUser(1L);
// 더 이상 상호작용 없음 검증
verify(userRepository).findById(1L);
verifyNoMoreInteractions(userRepository);
// 전혀 상호작용 없음 검증
verifyNoInteractions(emailService);
// 시간 제한 내 호출 검증
verify(userRepository, timeout(1000)).save(any());
verify(userRepository, timeout(1000).times(1)).save(any());
}
5.4 ArgumentCaptor
@Test
void argumentCaptor() {
// ArgumentCaptor 생성
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
// 서비스 호출
userService.createUser("John", "john@email.com");
// 인수 캡처 및 검증
verify(userRepository).save(userCaptor.capture());
User capturedUser = userCaptor.getValue();
assertEquals("John", capturedUser.getName());
assertEquals("john@email.com", capturedUser.getEmail());
// 여러 호출 시 모든 인수 캡처
ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
verify(emailService, times(3)).sendEmail(stringCaptor.capture());
List<String> allEmails = stringCaptor.getAllValues();
assertEquals(3, allEmails.size());
}
6. 고급 Mockito 기능
6.1 Answer 인터페이스
@Test
void customAnswers() {
// Answer 인터페이스로 복잡한 로직 구현
when(userRepository.findById(anyLong())).thenAnswer(invocation -> {
Long id = invocation.getArgument(0);
if (id <= 0) throw new IllegalArgumentException();
return new User(id, "User" + id);
});
// 인수 기반 답변
when(calculator.add(anyInt(), anyInt())).thenAnswer(invocation -> {
int a = invocation.getArgument(0);
int b = invocation.getArgument(1);
return a + b;
});
}
6.2 Partial Mocking
@Test
void partialMocking() {
UserService realService = new UserService(userRepository);
UserService spyService = spy(realService);
// 일부 메서드만 Mock
doReturn("mocked").when(spyService).generateId();
// 나머지는 실제 메서드 호출
String result = spyService.createUser("John");
}
6.3 Mock 설정
@Test
void mockSettings() {
// 이름 부여 (디버깅용)
UserRepository namedMock = mock(UserRepository.class, "UserRepo Mock");
// 기본 답변 설정
UserRepository mockWithDefaults = mock(UserRepository.class, RETURNS_DEEP_STUBS);
// 엄격한 Mock (불필요한 stubbing 검출)
UserRepository strictMock = mock(UserRepository.class, withSettings().strictness(Strictness.STRICT_STUBS));
}
7. BDD 스타일 (BDDMockito)
import static org.mockito.BDDMockito.*;
@Test
void bddStyle() {
// given (준비)
given(userRepository.findById(1L)).willReturn(user);
given(emailService.isValidEmail("test@email.com")).willReturn(true);
// when (실행)
User result = userService.getUser(1L);
// then (검증)
then(userRepository).should().findById(1L);
then(emailService).should(never()).sendEmail(anyString());
then(userRepository).shouldHaveNoMoreInteractions();
}
8. 테스트 더블 종류와 특징
8.1 Dummy
// 단순히 인스턴스화만 필요한 객체
User dummy = mock(User.class); // 메서드 호출 안함
8.2 Fake
// 실제 동작하는 구현체 (단순화된 버전)
class FakeUserRepository implements UserRepository {
private Map<Long, User> users = new HashMap<>();
@Override
public User save(User user) {
users.put(user.getId(), user);
return user;
}
}
8.3 Stub
// 미리 준비된 답변만 제공
when(userRepository.findById(1L)).thenReturn(user);
8.4 Mock
// 행위 검증이 주목적
verify(userRepository).save(any(User.class));
8.5 Spy
// 실제 객체의 일부분만 Mock
List<String> spyList = spy(new ArrayList<>());
when(spyList.size()).thenReturn(100);
9. 실무 활용 패턴
9.1 Service Layer 테스트
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository userRepository;
@Mock private EmailService emailService;
@InjectMocks private UserService userService;
@Test
void shouldCreateUserSuccessfully() {
// given
String name = "John";
String email = "john@test.com";
User expectedUser = new User(1L, name, email);
given(userRepository.save(any(User.class))).willReturn(expectedUser);
given(emailService.isValidEmail(email)).willReturn(true);
// when
User result = userService.createUser(name, email);
// then
assertThat(result.getName()).isEqualTo(name);
then(userRepository).should().save(any(User.class));
then(emailService).should().sendWelcomeEmail(email);
}
}
9.2 통합 테스트와 Mock 조합
@SpringBootTest
class IntegrationTest {
@Autowired private UserService userService;
@MockBean private EmailService emailService; // Spring Boot의 Mock
@Test
void integrationTestWithMock() {
// given
given(emailService.sendEmail(any())).willReturn(true);
// when & then
userService.registerUser("test@email.com");
verify(emailService).sendEmail(argThat(email ->
email.contains("Welcome")));
}
}