Mockito란?
Mockito는 자바에서 테스트용으로 사용되는 Mocking 프레임워크입니다.
여러 Mock객체를 대신 간편하게 만들어 줘서 사용할 수 있습니다.
대체로 A라는 클래스가 여러 의존성을 가지고 있을 때 A에 대한 클래스만 집중하여 테스트 하고 싶을 때 다른 의존성들을 전부 Mock으로 변환시켜 사용하게 됩니다. (결국 클래스 단위 테스트를 진행하고자 할 때 사용하게 됩니다.)
Mockito를 사용하기 위해선 외부 라이브러리인 만큼 주입을 해줘야 겠죠
Mockito Dependency
// Gradke
implementation 'org.mockito:mockito-core:5.4.0'
// Maven
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
MockitoExtension.class vs MockitoAnnotations.openMocks()
먼저 Mockito를 사용하기 위해서는 mock을 사용한다고 알려줘야하는데 2가지 방법이 있습니다.
@BeforeEach
void initInjection() {
MockitoAnnotations.openMocks(this);
}
이렇게 하는 경우에는 해당 클래스에서 Mock을 사용한 다는 것을 알려주는 것입니다.
또 다른 방법으로는
@ExtendWith(MockitoExtension.class)
ExtendWith 어노테이션을 붙여주는 것인데 이것이 가능한 이유는
ExtendWith에서 이러한 API를 상속받는 클래스에 대해 자동으로 적용을 해주는데

Mockito가 이러한 기능을 사용할 수 있도록 MockitoExtension
이라는 클래스를 지원해 주기 때문이다.

Mock 주입
Mock을 주입하는 방식에 따라 나눌 수 있는데, @Mock
, @Spy
, @InjectMocks
가 있습니다.
@Mock
말 그대로 mock객체를 만들어 주는 어노테이션이다.
List mockList = Mockito.mock(ArrayList.class);
// or field로
@Mock
private CustomMock customMock;
내부적으로 메서드를 invoke하여 메서드들의 정보만 가져온다.
따라서 실제로 메서드를 실행 시킬 수 있지만, 동작은 하지 않는다.
아래와 같은 CustomMock객체가 있다고 해보자
@Component
public class CustomMock {
private static final String staticField = "Static field";
private String field = "instance field";
public CustomMock() {
System.out.println("[Custom Mock]");
System.out.println(staticField);
System.out.println(field);
}
public void run() {
int a = getInt();
System.out.println("Custom Mock is run : "+a);
System.out.println(test());
}
public int test() {
return 1000;
}
private int getInt() {
return 100;
}
}
이것을 mocking해서 값을 봐보면 내부에는 field가 null로 되어 있고 나머지는 존재하지 않는 것을 볼 수 있다.

즉 내부적으로 어떠한 값고 가질 수 없는 상태인 것이다.
@Test
void mockTest() {
Integer result = customMock.test();
verify(customMock).test();
log.info("반환 값은? : {}", result);
}
따라서 아래와 같이 동작하게 되면,

와 같이 반환 값이 0인 것을 볼 수 있다.
결국 CustomMock에 대한 생성자를 호출하지 않은 것을 볼 수 있고,
또한 verify
를 사용했는데, verfiy
의 경우 customMock이 특정 메서드를 실행했는지 확인하는 메서드이며, 만약
@Test
void mockTest() {
Integer result = customMock.test();
verify(customMock).run();
log.info("반환 값은? : {}", result);
}
위와 같이 실행하지 않는 메서드를 확인하게 되면

실패하게 된다.
결과적으로, Mock
은 똑같은 객체를 생성하게 되지만, 내부적으로 동작만 가능할 뿐 다른 추가적인 작업이 불가능 하다 볼 수 있다.
@Spy
실제 메서드 들을 invoke하는데, 실제 인스턴스를 가져온다 생각하면 될 것 같습니다.
@Component
public class CustomSpy {
private static final String staticField = "Static field";
private String field = "instance field";
public CustomSpy() {
System.out.println("[Custom Spy]");
System.out.println(staticField);
System.out.println(field);
}
public void run() {
int a = getInt();
System.out.println("Custom Spy Mock is run : " + a);
System.out.println(test());
}
public int test() {
return 1000;
}
private int getInt() {
return 100;
}
}
@Test
void spyTest() {
customSpy.run();
verify(customSpy).test();
given(customSpy.test()).willReturn(400);
customSpy.run();
}
위 코드를 실행하게 되면


위와 같이 생성자가 정상적으로 실행 되는 것을 볼 수 있고, 내부적으로는 field값이 생성되어 있는 것을 볼 수 있습니다.
또 다른 특징으로는 내부적으로 메서드가 invoke로 실제 메서드를 동작하게 하더라도 값을 변경할 수가 있고 아래와 같은 결과를 확인할 수 있습니다.

여기서 주의해야할 점은Spy
의 경우 내부적으로 추가적인 mock객체를 넣어줄 수 없다는 점이 있습니다.

@InjectMock
InjectMock
의 경우 의존성을 주입해 주는 어노테이션 입니다. 따라서 Mock객체를 생성하는 Spy
나 Mock
어노테이션과는 다르다는 것을 알 수 있습니다.
근데 생각해보면 우리는 지금까지 @InjectMock만 붙여줬지 다른 어노테이션을 붙이지는 않았는데, 위에서 설명드렸던 것 처럼 생성자 주입만 하기때문에 에초에 Mock인스턴스를 만들지 않아서 만들어 지지 않아야 정상입니다.
이건 사실 @InjectMock의 인스턴스가 초기화 되기 전에 실행되면 자동으로 생성자를 호출해주게 됩니다.
추가적인 내용이나 생성자의 유무와 동작에 따른 원리를 알고 싶으시면 @InjectMocks
를 확인해 보시면 됩니다.
@InjectMock을 통한 예시를 보여 드리면
@Service
public class CustomService {
private final CustomInject injectMock;
private final CustomSpy customSpy;
private final CustomMock customMock;
public CustomService(CustomInject customInjectMock, CustomSpy customSpy, CustomMock customMock) {
System.out.println("[Custom Service]");
this.injectMock = customInjectMock;
this.customMock = customMock;
this.customSpy = customSpy;
}
public void run() {
int a = getInt();
System.out.println(injectMock == null);
System.out.println(a);
System.out.println(test());
}
public int test() {
return 1000;
}
private int getInt() {
return 100;
}
}
따라서 run을 실행하게 되면

각 객체가 정상적으로 인스턴스가 생성되고 값을 주입 받은 것을 확인할 수 있고

와 같은 결과가 나옵니다.
@Test
void injectTest() {
customService.run();
customInjectMock.run();
verify(customService).test(); // 에러
verify(customInjectMock).test(); // 에러
}
다만 여기서 verify는 error가 발생하는데, 실제 인스턴스를 주입하는 InjectMock은 mock이 아니기 때문에 사용할 수 없습니다.
추가
여러 에러를 한번에 확인
각각의 에러를 확인하기 위해 assertj
를 사용하게 되면 처음 만난 에러에 멈추는 경우가 있습니다. 여러 값들을 비교 했을 경우 한번에 확인하여 고치고 싶은데 못하는 경우가 발생하죠.
이러한 문제를 피할 수 있는 방법으로 junit
의 assertAll
이 있습니다.
따라서 위에서 있던 2개의 verify에러같은 경우에도
assertAll("Inject test",
() -> {
verify(customService).test();
verify(customInjectMock).test();
}
);
---
assertAll(
() -> verify(customService).test(),
() -> verify(customInjectMock).test()
);
위와 같이 2가지 방식으로 해결할 수 있습니다. 처음 같은 경우에는 에러에 대한 Display name을 지정해 주는 것이고 아래는 그냥 출력해 주는 방식 입니다.
@Capture
Capture라는 어노테이션도 존재하는데, Capture의 경우 이전에 실행 했던 메서드에서 인자로 전달한 값을 그대로 가져와서 사용하기 위함입니다.
예를들어 인자로 받은 값을 그대로 넘겨주는 메서드를 작성한다고 하자
public int test2(int a) {
return a;
}
와 같은 코드가 있다면
int result = customSpy.test2(100);
verify(customSpy).test2(captor.capture());
assertThat(result).isEqualTo(captor.getValue());
위와 같이 코드를 작성하게 되면 result = 100
이고 captor.getValue()또한 100 이므로 테스트가 통과하게 된다.
'JAVA > test' 카테고리의 다른 글
Java - 시간을 다루기 및 테스트 (2) | 2023.08.06 |
---|