JUnit, Mockito, 유닛 테스트

메밀·2024년 5월 19일
post-thumbnail

1. 기초 개념

1) 모의 객체

- @ExtendWith(MockitoExtension.class)

  • Mockito 어노테이션(@Mock, @InjectMocks, @Spy)으로 모의 객체를 생성/주입할 수 있게 해주는 어노테이션
  • 아래 코드와 같은 역할
@BeforeEach
void setUp() {
    MockitoAnnotations.openMocks(this);
}

- @Mock

  • 모의 객체를 생성
  • 실제 객체처럼 동작하지만, 프로그래머가 직접 제어 가능
  • 주로 테스트 대상 클래스가 의존하는 객체를 대체하여 테스트의 격리성을 유지하는 데 사용

- @InjectMock

  • 테스트 대상 클래스에 의존성 주입

2) verify: 메서드 호출 검증

		// 테스트 대상 메소드에서 PlantService.save 메소드가 호출되었는지 검증
        verify(plantService).save(plant);
        
        // 특정 횟수로 호출되었는지 검증
        verify(plantService, times(1)).save(plant);

        // 다른 메서드 호출이 없어야 함을 검증
        verifyNoMoreInteractions(plantService);

3) when().thenReturn(): Stubbing

특정 메서드가 호출될 때 반환할 값이나 예외 정하기

// 반환값 지정
when(plantService.findById(plantId)).thenReturn(new Plant("몬스테라"));

// 예외
when(plantService.findById(plantId)).thenThrow(NoSuchElementException.class);

4) Assertions

예상한 동작이 실제로 일어나는지를 검증
static import해서 쓰면 편하다.
expected, actual 순으로 파라미터로 넘겨주면 됨

assertEquals("expected result", result);
assertTrue(result);
assertFalse(result);
assertNotNull(result);

- assertThrow

Executable executable = () -> repotCommandService.getRepotByRepotIdAndGardenerId(repotId, gardenerId);
        UnauthorizedAccessException e = assertThrows(UnauthorizedAccessException.class, executable);
        assertEquals(ExceptionCode.NOT_YOUR_REPOT, e.getCode());

단순히 해당 Exception이 발생하는지 검사하려면 assertThrows(예외, 람다) 정도면 충분하지만,
Exception 내부도 검사하고 싶거나 코드가 너무 길어 나눠서 쓰고 싶다면 위처럼 하면 된다.

2. @ParameterizedTest

동일한 테스트 메서드를 여러 다른 입력값으로 실행하여 반복 테스트하기

- @ParameterizedTest

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4, 5})
    void testIsPositive(int number) {
        assertTrue(number > 0);
    }

ints 배열에 있는 각각의 값들을 number 매개변수로 넘겨주고, 이 값을 검증하여 양수인지를 확인

- @MethodSource

인자를 제공하는 메서드를 지정

    @ParameterizedTest
    @MethodSource("provideNumbers")
    void testIsPositive(int number) {
        assertTrue(number > 0);
    }

    static Stream<Integer> provideNumbers() {
        return Stream.of(1, 2, 3, 4, 5);
    }

- @ArgumentsSource

인자를 제공하는 클래스 지정

@ParameterizedTest
    @ArgumentsSource(MyArgumentsProvider.class)
    void testIsPositive(int number) {
        assertTrue(number > 0);
    }

    static class MyArgumentsProvider implements ArgumentsProvider {
        @Override
        public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
            return Stream.of(
                    Arguments.of(1),
                    Arguments.of(2),
                    Arguments.of(3),
                    Arguments.of(4),
                    Arguments.of(5)
            );
        }
    }

- 요약

@MethodSource@ArgumentSource
"메소드 이름"MyArgumentsProvider.class
return Stream<해당 타입>implements ArgumentsProvider

3. 기타 개념

- @DisplayName

테스트 이름 짓기

- doNothing()

Mock Object 메서드가 호출 시 아무 작업도 하지 않도록 설정
주로 void 메서드가 호출 시 활용

        doNothing().when(gardenerRepository).delete(anyLong());

실제로 데이터베이스에서 사용자를 삭제하지 않고, 단순히 메서드 호출 여부만을 검증할 수 있음

4. 정리

  • 테스트 클래스 세팅
    • 클래스 어노테이션으로 @ExtendWith(MockitoExtension.class)
      • 혹은 @BeforeEach 메소드 작성
    • 테스트 대상 클래스에 @InjectMock
    • 테스트 대상 클래스의 의존관계 클래스에 @Mock
  • 테스트 클래스 설정
    • @Test: 기본 테스트
    • @ParameterizedTest: 각기 다른 테스트 데이터를 사용해 테스트
      • @MethodSource("메소드 이름"): 메소드를 통해 인자 제공
      • @ArgumentProvider(대상 클래스명.class): 클래스를 통해 인자 제공
  • Given(주어진 상황)
    • 테스트 대상 객체, 상태 정의, 입력값 생성
    • stubbing
      • when().thenReturn()
      • when().thenThrow()
  • When(실행)
    • GardenerDetail result = gardenerService.update(request);
  • Then(검증)
    • Assertions
      • assertEqual, assertTrue, assertFalse, assertNotNull ...
    • verify
      • verify(plantService, times(1)).save(plant);
      • verifyNoMoreInteractions(plantService);

0개의 댓글