DEV Community

dss99911
dss99911

Posted on Originally published at dss99911.github.io

Mockito로 Java 단위 테스트 작성하기

Mockito는 Java에서 가장 많이 사용되는 모킹(Mocking) 프레임워크입니다. 의존성을 모의 객체로 대체하여 단위 테스트를 격리된 환경에서 실행할 수 있게 해줍니다.

1. 초기 설정

의존성 추가

testImplementation 'org.mockito:mockito-core:5.8.0'
testImplementation 'junit:junit:4.13.2'
Enter fullscreen mode Exit fullscreen mode

테스트 클래스 설정

@RunWith(MockitoJUnitRunner.class)
public class PortfolioTester {
    // 테스트 코드
}
Enter fullscreen mode Exit fullscreen mode

Mockito 클래스의 멤버들을 static import해서 사용합니다:

import static org.mockito.Mockito.*;
Enter fullscreen mode Exit fullscreen mode

2. Mock 생성

어노테이션 방식

@Mock
StockService stockService;
Enter fullscreen mode Exit fullscreen mode

프로그래매틱 방식

stockService = mock(StockService.class);
Enter fullscreen mode Exit fullscreen mode

Mock Injection

// @InjectMocks: mock을 생성하고 주입
@InjectMocks
MathApplication mathApplication = new MathApplication();

// @Mock: 주입될 mock 객체
@Mock
CalculatorService calcService;
Enter fullscreen mode Exit fullscreen mode

3. Mocking Method

기본 when/thenReturn

when(stockService.getPrice(googleStock)).thenReturn(50.00);
when(stockService.getPrice(microsoftStock)).thenReturn(1000.00);
Enter fullscreen mode Exit fullscreen mode

Argument Matcher

// 어떤 int 값이든 매칭
when(list.get(anyInt())).thenReturn("dfdf");

// 다양한 matcher
any(), anyString(), anyList(), anyMap()
eq(value)  // 특정 값
Enter fullscreen mode Exit fullscreen mode

연속 호출시 다른 값 반환

when(stockService.getPrice(googleStock))
    .thenReturn(50.00)
    .thenReturn(100.00)
    .thenReturn(150.00);
// 첫 호출: 50.00, 두 번째: 100.00, 세 번째: 150.00
Enter fullscreen mode Exit fullscreen mode

예외 발생

doThrow(new RuntimeException("divide operation not implemented"))
    .when(calcService).add(10.0, 20.0);
Enter fullscreen mode Exit fullscreen mode

커스텀 응답 (Answer)

when(calcService.add(any(), eq(10.0))).thenAnswer(new Answer<Double>() {
    @Override
    public Double answer(InvocationOnMock invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        Object mock = invocation.getMock();
        return 30.0;
    }
});
Enter fullscreen mode Exit fullscreen mode

4. Spy

실제 객체를 사용하되, 호출을 감시합니다:

Calculator calculator = new Calculator();

// 실제 인스턴스를 spy로 감싸기
calcService = spy(calculator);

// 실제 메서드가 호출됨
Assert.assertEquals(mathApplication.add(20.0, 10.0), 30.0, 0);

// 특정 메서드만 stub
doReturn(100.0).when(calcService).add(10.0, 20.0);
Enter fullscreen mode Exit fullscreen mode

Spy vs Mock

  • Mock: 모든 메서드가 stub됨 (기본값 반환)
  • Spy: 실제 메서드 호출, 필요한 것만 stub

5. Verify

기본 검증

Assert.assertEquals(mathApplication.add(10.0, 20.0), 30.0, 0);

// 특정 인자로 호출되었는지 확인
verify(calcService).add(10.0, 20.0);
Enter fullscreen mode Exit fullscreen mode

호출 횟수 검증

verify(calcService, times(1)).add(10.0, 20.0);
verify(calcService, never()).multiply(10.0, 20.0);

// 기타 옵션
atLeast(2)      // 최소 2회
atLeastOnce()   // 최소 1회
atMost(3)       // 최대 3회
Enter fullscreen mode Exit fullscreen mode

호출 순서 검증

InOrder inOrder = inOrder(calcService);

// add가 먼저 호출되고, 그 다음 subtract 호출 확인
inOrder.verify(calcService).add(20.0, 10.0);
inOrder.verify(calcService).subtract(20.0, 10.0);
Enter fullscreen mode Exit fullscreen mode

처리 시간 검증

// 100ms 이내에 호출되었는지 확인
verify(calcService, timeout(100)).add(20.0, 10.0);
Enter fullscreen mode Exit fullscreen mode

Mock 초기화

reset(calcService);
Enter fullscreen mode Exit fullscreen mode

6. ArgumentCaptor

호출 시 전달된 인자를 캡처합니다:

@Captor
private ArgumentCaptor<Callback<List<Repository>>> captor;

@Test
public void test_getRepositories() {
    // arrange
    String user = "testUser";
    List<Repository> repositories = new ArrayList<>();
    repositories.add(new Repository());

    when(gitHubAPI.GetRepos(user)).thenReturn(mockCall);
    Response<List<Repository>> response = Response.success(repositories);

    // act
    presenter.start();

    // assert - 콜백 캡처
    verify(mockCall).enqueue(captor.capture());
    captor.getValue().onResponse(null, response);

    verify(progressBarProvider).showProgressBar();
    verify(progressBarProvider).hideProgressBar();
    verify(view).updateRepositoryList(repositories);
}
Enter fullscreen mode Exit fullscreen mode

7. API 테스트 전체 예제

public class RepositoryUnitTest {

    @Mock
    private RepositoryContract.View view;

    @Mock
    private ProgressBarProvider progressBarProvider;

    @Mock
    private GitHubAPI gitHubAPI;

    @Mock
    private Call<List<Repository>> mockCall;

    @Captor
    private ArgumentCaptor<Callback<List<Repository>>> captor;

    private RepositoryPresenter presenter;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        presenter = new RepositoryPresenter(gitHubAPI, progressBarProvider);
        presenter.setView(view);
    }

    @Test
    public void test_getRepositories() {
        // arrange
        String user = BuildConfig.GITHUB_OWNER;
        List<Repository> repositories = new ArrayList<>();
        repositories.add(new Repository());

        when(gitHubAPI.GetRepos(user)).thenReturn(mockCall);
        Response<List<Repository>> response = Response.success(repositories);

        // act
        presenter.start();

        // assert
        verify(mockCall).enqueue(captor.capture());
        captor.getValue().onResponse(null, response);

        verify(progressBarProvider).showProgressBar();
        verify(progressBarProvider).hideProgressBar();
        verify(view).updateRepositoryList(repositories);
    }
}
Enter fullscreen mode Exit fullscreen mode

8. 베스트 프랙티스

AAA 패턴

  • Arrange: 테스트 준비
  • Act: 테스트 대상 실행
  • Assert: 결과 검증

테스트당 하나의 개념

  • 하나의 테스트는 하나의 동작만 검증
  • 여러 assert보다 여러 테스트 메서드가 낫다

Mock vs Stub

  • Mock: 행위 검증 (verify)
  • Stub: 상태 검증 (assert on result)

참고 자료


Originally published at https://dss99911.github.io

Top comments (0)