Mockito 테스트 (1/2)

다음 편: [테스트] 2. Mockito Stubbing 심화

Service를 테스트하고 싶은데, Repository가 DB에 연결되어 있다. DB 없이 Service 로직만 테스트하려면 어떻게 할까? 가짜 객체를 만들어서 끼워 넣으면 된다. 이걸 Mock이라 하고, Mockito는 Mock을 쉽게 만들어주는 라이브러리다.

Mock이란

Mock은 "진짜처럼 행동하는 가짜 객체"다.

FeedServiceImplFeedRepository에 의존한다. 테스트에서 진짜 FeedRepository를 쓰면 DB가 필요하다. Mock을 쓰면 DB 없이 "findById를 호출하면 이 값을 돌려줘"라고 미리 정해둘 수 있다.

기본 사용법

Mockito에서 가장 많이 쓰는 두 어노테이션이다.

@Mock

가짜 객체를 만든다.

@Mock
private FeedRepository feedRepository;

이렇게 선언하면 Mockito가 FeedRepository의 가짜 구현체를 만들어준다. 모든 메서드가 기본값을 반환한다. 참조 타입은 null, 숫자는 0, boolean은 false다.

@InjectMocks

Mock들을 주입받는 테스트 대상을 지정한다.

@InjectMocks
private FeedServiceImpl feedService;

Mockito가 FeedServiceImpl의 실제 객체를 만들고, 위에서 선언한 Mock들을 자동으로 끼워 넣는다.

이 두 어노테이션을 함께 쓰면 테스트 클래스는 이런 모양이 된다.

@ExtendWith(MockitoExtension.class)
class FeedServiceImplTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private WeatherRepository weatherRepository;

    @Mock
    private FeedRepository feedRepository;

    @Mock
    private FeedClothesRepository feedClothesRepository;

    @InjectMocks
    private FeedServiceImpl feedService;
}

@ExtendWith(MockitoExtension.class)는 JUnit 5에서 Mockito를 활성화하는 설정이다. 이게 없으면 @Mock 어노테이션이 동작하지 않는다.

리플렉션

Mockito가 Mock을 주입하는 방식을 이해하려면, 리플렉션을 알아야 한다.

리플렉션은 영어로 Reflection이라 한다. 프로그램이 실행 중에 자기 자신의 구조를 들여다보고 조작하는 기능이다.

보통 private 필드는 외부에서 접근할 수 없다.

private UserRepository userRepository; // 외부에서 접근 불가

그런데 리플렉션을 쓰면 런타임에 이 제약을 무시할 수 있다.

Field field = FeedServiceImplTest.class.getDeclaredField("userRepository");
field.setAccessible(true);  // private 접근 제한 해제
field.set(testInstance, mockObject);  // 강제로 값 주입

Mockito의 @Mock이 정확히 이 방식으로 동작한다. 테스트 클래스의 필드 목록을 훑으면서 @Mock이 붙은 필드를 찾고, 리플렉션으로 Mock 객체를 넣어준다.

리플렉션은 Java의 정상적인 접근 제어를 우회하는 기능이다. 테스트나 프레임워크 내부에서 쓰는 건 괜찮지만, 일반 비즈니스 코드에서는 쓰지 않는 게 좋다.

final이 안 되는 이유

리플렉션을 이해하면 final이 왜 안 되는지도 바로 알 수 있다.

final 필드는 한번 값이 할당되면 바꿀 수 없다. Java가 "이 값은 절대 변하지 않는다"고 보장하는 키워드다. 리플렉션의 field.set()final 앞에서는 거부된다.

Mockito는 리플렉션으로 값을 넣어야 하니까, final이면 Mock을 주입할 수가 없다. 테스트가 실행되지만 필드에는 null이 들어가 있어서 NullPointerException이 터진다.

자주 하는 실수

final 필드에 @Mock을 붙인다

Mockito는 리플렉션으로 값을 주입하는데, final이면 값을 바꿀 수 없다. 반드시 final을 제거해야 한다.

```java

// ❌ final이면 Mock 주입 불가

@Mock

private final FeedRepository feedRepository;

// ✅ final 제거

@Mock

private FeedRepository feedRepository;

```

[!DANGER] @InjectMocks에 인터페이스 타입을 쓴다

@InjectMocks는 실제 객체를 new로 생성해야 한다. 인터페이스는 인스턴스를 만들 수 없으므로 반드시 구현체 타입을 써야 한다.

```java

// ❌ 인터페이스 — 인스턴스 생성 불가

@InjectMocks

private FeedService feedService;

// ✅ 구현체

@InjectMocks

private FeedServiceImpl feedService;

```