반응형
해당 글에서는 Mockito에 대해 이해하고 활용하는 방법에 대해 확인해 봅니다.
💡 [참고] 이전에 작성한 Test 관련 글들을 읽으시면 도움이 됩니다.
분류 | 링크 |
JUnit 5 이론 및 구성 요소 | https://adjh54.tistory.com/341 |
JUnit 5 환경구성 및 활용예제 | https://adjh54.tistory.com/342 |
JUnit 5 + Mockito 이론 및 활용예제 | https://adjh54.tistory.com/346 |
JUnit 5 + MockMvc 이론 및 활용예제 | https://adjh54.tistory.com/347 |
Assertions API Document | https://adjh54.tistory.com/348 |
개발방법론 TDD, BDD | https://adjh54.tistory.com/305 |
JUnit 5 + Mockito + MockMVC 사용예제 Github | https://github.com/adjh54ir/blog-codes/tree/main/spring-boot-test |
1) Mockito
💡 Mockito
- 단위 테스트를 위해 모의 객체를 생성하고 관리하는 데 사용되는 Java 오픈소스 프레임워크를 의미합니다.
- 이를 사용하면 실제 객체의 동작을 모방하는 모의 객체(Mock Object)를 생성하여 코드의 ‘특정 부분을 격리’시키고 테스트하기 쉽게 만들어줍니다.
- 주로 단일 컴포넌트의 동작을 테스트하는 데 사용되며 클래스 내의 개별 메서드나 함수, 서로 다른 클래스 또는 컴포넌트 간의 상호작용, 객체들 간의 협업 등을 테스트할 수 있습니다.
[ 더 알아보기 ]
💡모의 객체(Mock Object)
- 실제 사용되는 객체 생성을 대체하기 위해 테스트에 사용되는 객체를 의미합니다.
- 일반적으로 모의 객체의 변수 값은 null, 0, false와 같은 기본 타입의 값이 반환되며 메서드는 기본적으로 null을 구성이 됩니다.
1. Mockito를 이용한 테스트 목적
💡 Mockito를 이용한 테스트 목적
- Mockito를 사용하여 ‘모의 객체’와 함께 서비스를 호출하여 ‘비즈니스 로직이 올바르게 처리’가 되는지 확인하기 위해 테스트를 수행합니다.
- 이러한 테스트 과정을 통해 서비스의 비즈니스 로직에 대해 검증하고 예외 상황에 대해 처리를 확인합니다.
2) JUnit5 흐름 / JUnit 5 + Mockito 흐름
1. JUnit5 흐름
💡 JUnit5 흐름
- 하단의 그림은 Mockito를 사용하지 않고 JUnit5만을 사용하여서 서비스를 테스트한 테스트 케이스의 흐름을 보여줍니다.
- 이러한 흐름은 @Test를 수행하는 testsIsNotNullCodeList()라는 메서드에서는 서비스 호출을 위해 ‘Local Server’를 실행시켜서 DB 데이터를 조회해 옵니다.
- 해당 흐름에서는 매번 데이터 조회를 해오기 위해 ‘로컬 서버’를 실행시켜 테스트를 수행하는 데에 불편함이 발생하고 이를 대처하여 Mockito를 사용합니다.
2. JUnit 5 + Mockito 흐름
💡 JUnit 5 + Mockito 흐름
- 하단의 그림은 Mockito를 사용하여 서비스를 테스트한 테스트 케이스의 흐름을 보여줍니다.
- 이러한 흐름은 @Test를 수행하는 TestMethod()라는 메서드에서 서비스 호출을 위해 getCodeList() 함수를 호출하지만 DB에서 데이터를 조회해오지 않는 형태입니다.
- 이 과정에서는 JUnit5만을 이용하였던 방식과 다르게 직접적인 DB 호출을 수행하지 않고 Mock Object라는 모의 객체를 구성하여서 테스트를 진행하는 방식입니다.
3) Mockito 수행과정
💡 Mockito 수행과정
- 모의 객체 생성(Mock) → 메서드 호출 예상 동작 설정(Stub) → 메서드 호출 검증(Verify)
1. 모의 객체 생성 : Mock
- Mockito를 사용하여 테스트에 필요한 '모의(가짜) 리스트'를 생성합니다.
- MockList의 경우는 실제 리스트와 동일한 방식으로 동작을 하지만, 이 동작을 사전에 정의 할 수 있습니다.
2. 메서드 호출 예상 동작 설정 : Stub
- 모의 객체의 메서드 호출에 대한 ‘예상 동작’을 정의합니다.
- 예상 동작으로 MockList에서 size() 메서드가 호출이 되었을 경우, 5를 반환하도록 지정하였습니다.
- 그리고 하단에 실제 mockList.size()를 호출 하였을때 5의 값이 추출되었습니다.
3. 메서드 호출 검증 : Verify
- 모의 객체에 대해 특정 메서드가 호출되고 예상된 인자와 함께 호출되었는지를 검증하는 메서드를 제공합니다.
- 해당 경우는 mockList 내에 .size() 메서드가 호출이 되었는지 검증을 수행합니다.
@Test
@DisplayName("기본 요소를 테스트 해봅니다.")
void testSimple() {
// 1. 모의 객체 생성 : Mock
List<String> mockList = Mockito.mock(List.class);
// 2. 메서드 호출 예상 동작 설정 : Stub
Mockito.when(mockList.size()).thenReturn(5);
System.out.println("동작 값을 확인 합니다 :: " + mockList.size()); // 동작 값을 확인 합니다 :: 5
// 3. 메서드 호출 검증 : Verify
Mockito.verify(mockList).size();
}
4) Mockito 주요 용어
1. 용어 요약
용어 | 분류 | 설명 |
Mock | 용어 | 실제 객체를 대신하여 프로그래밍을 테스트할 수 있는 모의 객체를 생성하는 것으로 특정 동작이나 결과를 설정하고 검증하기 위해 사용됩니다. |
Stub | 용어 | 특정 메서드 호출에 대해 미리 정의된 동작을 반환하는 객체로, 테스트에서 사용될 때 실제 동작이 아닌 가짜 동작을 수행합니다. |
Spy | 용어 | 실제 객체를 사용하면서 해당 객체의 일부 동작을 감시하고 기록할 수 있는 객체입니다. |
Mocking | 용어 | 특정 동작이나 결과를 시뮬레이션하기 위해 모의 객체를 생성하거나 가짜 동작을 정의하는 것을 말합니다. |
Verification | 용어 | 메서드 호출이나 객체 동작이 예상대로 수행되었는지 확인하는 작업입니다. |
Matchers | 메서드 | 모킹이나 확인 작업에서 사용되는 매개변수 일치 여부를 확인하는 데 사용되는 메서드를 제공합니다. |
MockitoAnnotations | 클래스 | Mockito 애너테이션을 사용하여 Mock 및 Spy 객체를 초기화하는 데 사용되는 클래스입니다. |
MockitoJUnitRunner | 클래스 | JUnit 테스트에 Mockito를 사용하는 데 필요한 설정을 자동으로 처리하는 러너 클래스입니다. |
MockitoJUnitRunner.Silent | 클래스 | MockitoJUnitRunner와 유사하지만, Mock 객체를 생성하지 않은 클래스에서도 실행할 수 있습니다. |
2. Mock
💡 Mock
- 실제 객체를 대체하는 ‘모의 객체’로 기대하는 동작을 설정하고 검증을 위해 사용이 됩니다.
- 이러한 Mock로 모의 객체를 생성하고 Stub로 예상 동작을 정의하며 verfiy를 통해 검증을 수행하는 Mockito의 수행과정입니다.
- 실제 객체의 동작을 제어하는 방식으로 동작을 시뮬레이션하는 객체입니다.
- 메서드 호출의 예상 동작을 정의하고 올바르게 호출되었는지 확인할 수 있습니다.
// Mock 객체 생성 : Mock - null의 값을 가지는 리스트가 생성됩니다.
List<String> mockList = Mockito.mock(List.class);
// Mock 객체의 동작 정의
Mockito.when(mockList.size()).thenReturn(5);
// Mock 객체 사용
int size = mockList.size(); // 5를 반환
3. Stub
💡 Stub
- 테스트 중에 모의 객체(Mock Object)의 동작을 정의하는 ‘예상 동작’을 설정하는 기능입니다.
- 이를 사용하여 메서드가 호출될 때 어떤 값을 반환하거나 어떤 예외를 던져야 하는지를 지정할 수 있습니다.
- 이를 통해 종속성의 동작을 제어하고 테스트 대상을 격리할 수 있습니다.
// Mock 객체 생성
List<String> mockList = Mockito.mock(List.class);
// Mock 객체의 동작 정의 : Stub - Mock 객체의 사이즈를 항상 10으로 반환하도록 예상동작을 설정합니다.
Mockito.when(mockList.size()).thenReturn(10);
// Mock 객체의 메소드 호출
int size = mockList.size();
// 결과 확인
assertEquals(10, size);
3. Spy
💡 Spy
- 기존 객체의 일부 메서드를 원본 동작을 유지하면서 변경하거나 감시할 수 있게 해주는 기능을 제공합니다.
- Spy를 사용하면 실제 객체를 생성하고 원하는 메서드를 호출할 수 있습니다. 이는 테스트 도중에 객체의 일부 동작을 감시하고, 특정 메서드 호출을 확인하거나 원하는 대로 메서드의 반환 값을 변경할 수 있는 유연성을 제공합니다.
// 실제 객체 생성
List<String> originalList = new ArrayList<>();
// Spy 객체 생성 : Spy - 실제 객체의 원본을 유지하며 객체를 생성합니다.
List<String> spyList = Mockito.spy(originalList);
// Spy 객체의 메서드 동작 정의 : Spy - 이러한 객체에 값을 지정합니다.
Mockito.doReturn("Mocked").when(spyList).get(0);
// Spy 객체 사용
String element = spyList.get(0); // "Mocked"를 반환
4. 모킹(Mocking)
💡 모킹(Mocking)
- 모의 객체의 생성을 통해 실제 객체의 동작을 모방하는 모의 객체를 만드는 과정을 의미합니다.
- 이러한 모의객체는 테스트 중인 코드를 격리하고 다양한 시나리오를 시뮬레이션하는 데 사용됩니다.
// 예시 클래스
public class ExampleClass {
public String getData() {
// 실제 동작을 하는 메소드
return "Real data";
}
}
// Mockito를 사용한 모의 객체 생성 예시
ExampleClass mockExample = Mockito.mock(ExampleClass.class);
// 모의 객체의 동작을 메소드의 반환 값으로 지정
Mockito.when(mockExample.getData()).thenReturn("Mocked data");
// 모의 객체를 사용하여 테스트
String result = mockExample.getData();
// 출력: "Mocked data"
System.out.println(result);
// 모의 객체의 메소드 호출 확인
Mockito.verify(mockExample).getData();
5. 검증(Verification)
💡 검증(Verification)
- 모의 객체의 메서드 호출을 확인하는 프로세스를 의미합니다.
- Verification을 사용하여 특정 메서드가 예상대로 호출되었는지를 확인할 수 있습니다.
💡 사용 예시
- ExampleClass라는 예시 클래스의 methodA()와 methodB()를 모의 객체로 대체하여 해당 메서드의 호출을 확인하고, 호출 순서를 검증할 수 있습니다.
// 예시 클래스
public class ExampleClass {
public void methodA() {
// 동작 A
}
public void methodB() {
// 동작 B
}
}
// Mockito를 사용한 Verification 예시
ExampleClass mockExample = Mockito.mock(ExampleClass.class);
// 메소드 호출
mockExample.methodA();
mockExample.methodB();
// 메소드 호출 확인
Mockito.verify(mockExample).methodA();
Mockito.verify(mockExample).methodB();
// 메소드 호출 순서 확인
InOrder inOrder = Mockito.inOrder(mockExample);
inOrder.verify(mockExample).methodA();
inOrder.verify(mockExample).methodB();
5) Mockito 어노테이션 & 메서드
1. Mockito 어노테이션
💡 Mockito 어노테이션
- Mockito에서 제공하는 주요 어노테이션입니다.
Annotation | 설명 |
@Mock | - 모의 객체(Mock Object)를 생성하는데 사용됩니다. |
@Spy | - 스파이 객체(Spy Object)를 생성하는데 사용됩니다. |
@Captor | - 모의 객체에 전달된 메서드 인수를 캡처하는데 사용됩니다. |
@InjectMocks | - 테스트 대상이 되는 객체에 ‘모의 객체를 자동으로 주입’할때 사용이 됩니다. |
@MockBean | - 스프링 프레임워크에서 사용되며, 테스트용 Mock 객체를 생성하고 주입하는 데 사용됩니다. |
@MockitoSettings | - Mockito의 설정을 변경하거나 커스터마이징 할 때 사용됩니다. |
@MockitoJUnitRunner | - JUnit 테스트에서 Mockito를 사용하기 위해 실행할 때 사용됩니다. |
@BDDMockito | - BDD 스타일의 테스트를 위해 Mockito를 사용할 때 사용됩니다. |
[ 더 알아보기 ]
💡 @InjectMocks, @Mock, @MockBean 차이점
- @InjectMocks, @Mock는 Mockito에서 ‘유닛 테스트’에 사용되고 @MockBean는 Spring Boot에서 ‘통합 테스트’에서 사용이 됩니다.@Mock는 특정 클래스나 인터페이스에 대해 ‘모의 객체를 생성’하는 역할을 수행합니다.
- @InjectMocks는 테스트 대상 객체에 ‘모의 객체를 주입’하는 역할을 수행합니다.
💡 모의 객체를 생성하는 것과 모의 객체를 주입한다의 차이는 무엇인가?
- @Mock와 같이 ‘모의 객체를 생성’한다는 것은 실제 객체와 동일한 메서드와 동작을 가지지만 실제 데이터나 외부 리소스와의 상호작용은 없습니다.
- @InjectMocks와 같이 ‘모의 객체를 주입’하는다는 것은 테스트의 대상이 특정 모의 객체를 사용해야 할 때, 그 모의 객체를 자동으로 주입하여 테스트를 수행할 수 있도록 합니다.
- 모의 객체를 주입하는 것은 @Mock로 생성한 모의 객체가 자동으로 주입되어 테스트가 진행됩니다.
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.MockBean;
import org.mockito.MockitoSettings;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.BDDMockito;
import org.junit.Test;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
public class ExampleClassTest {
@Mock
private Dependency dependencyMock;
@Spy
private Dependency dependencySpy;
@Captor
private ArgumentCaptor<String> captor;
@InjectMocks
private ExampleClass exampleClass;
@MockBean
private ExampleBean exampleBeanMock;
@MockitoSettings
private MockitoSettings settings;
@RunWith(MockitoJUnitRunner.class)
public class ExampleTestClass {
@Test
public void exampleTest() {
// Given
given(dependencyMock.getSomeValue()).willReturn("Mocked value");
// When
String result = exampleClass.doSomething();
// Then
verify(dependencySpy).doSomethingElse();
verify(exampleBeanMock).processValue(captor.capture());
String capturedValue = captor.getValue();
}
}
public class Dependency {
public String getSomeValue() {
return "Real value";
}
public void doSomethingElse() {
// ...
}
}
public interface ExampleBean {
void processValue(String value);
}
public class ExampleClass {
private Dependency dependency;
private ExampleBean exampleBean;
public ExampleClass(Dependency dependency, ExampleBean exampleBean) {
this.dependency = dependency;
this.exampleBean = exampleBean;
}
public String doSomething() {
String value = dependency.getSomeValue();
dependency.doSomethingElse();
exampleBean.processValue(value);
return value;
}
}
}
2. Mockito 메서드
💡 Mockito 메서드
- Mockito에서 제공하는 주요 메서드입니다. Mockito 객체의 메서드입니다.
메서드 | 설명 |
mock(Class<T> class) | - 주어진 클래스의 모의 객체를 생성합니다. |
doReturn(T value) | - 모의 객체의 특정 메서드 호출에 대한 반환 값을 정의합니다. |
when(T methodCall) | - 주어진 메서드 호출에 대한 스텁(stub)을 정의하여 예상동작을 정의하거나 검증할 수 있도록 합니다. |
thenReturn(T value) | - when() 메서드와 함께 사용하여 특정 메서드 호출에 대한 반환 값을 지정합니다. |
given(T methodCall) | - 모의 객체의 메서드 호출 동작을 정의합니다. when() 메서드와 동일한 역할을 합니다 |
verify(T mock) | - 주어진 모의 객체에 대한 메서드 호출 검증을 수행합니다. |
any(Class<T> clazz) | - 주어진 클래스에 대해 임의의 인스턴스를 나타내는 Matcher를 생성합니다. |
eq(T value) | - 주어진 값을 기준으로 매처(matcher)를 생성합니다. - value에는 null이나 원시 타입의 값 또는 객체가 포함될 수 있습니다. |
verifyNoMoreInteractions(T... mocks) | - 주어진 모의 객체들에 대한 추가적인 상호작용이 없는지 검증합니다. - mocks에는 검증할 모의 객체들의 목록을 전달합니다. |
💡 메서드를 활용한 간단한 예시
1. Mocking a class
- mock() 메서드를 통해서 Class라는 클래스를 Mock 클래스로 생성합니다. 이를 통해 클래스의 가짜 인스턴스를 생성합니다.
2. Setting up method call and return value
- 가짜 클래스에서 methodCall 메서드가 호출될 때 value라는 값을 반환하도록 설정합니다.
3. Verifying method call
- mockClass에서 methodCall이 any(Class.class)와 eq(value)와 함께 호출되었는지 확인합니다.
4. Verifying no more interactions
- 추가적인 상호작용이 없는 것을 확인하는 부분입니다. mockClass와 관련하여 추가적인 상호작용이 없는지 확인합니다.
@Test
void testSimple2() {
// 1. Mocking a class
List<String> mockList = Mockito.mock(List.class);
// 2. Setting up method call and return value
Mockito.doReturn(5).when(mockList).size();
System.out.println("Checking the behavior value :: " + mockList.size());
// 3. Verifying method call
Mockito.verify(mockList).size();
// 4. Verifying no more interactions
Mockito.verifyNoMoreInteractions(mockList);
}
6) Mockito: Given-When-Then 패턴
💡 Given-When-Then 패턴
- 테스트 케이스를 더 가독성 있고 유지보수하기 쉽도록 구조화하는 방법을 의미합니다.
- 이 패턴을 따르면 테스트 케이스가 더 구체적이고 이해하기 쉬워지며 각각의 단계를 분리하여 각 테스트 부분이 어떤 역할을 담당하는지 명확해집니다.
1. Mockito의 Given 단계
- 테스트 시나리오에서 ‘사전 조건’을 설정하는 부분입니다.
- 즉, 객체의 초기 상태나 호출된 메서드의 입력 값을 설정합니다. 예를 들어, Given 단계에서 모의 객체의 특정 메서드가 호출되었을 때 어떤 값을 반환하도록 설정할 수 있습니다.
2. Mockito의 When 단계
- 테스트할 메서드를 호출하는 부분입니다.
- 이 부분에서는 실제로 테스트 대상 메서드를 호출하여 원하는 ‘동작을 검증’합니다.
3. Mockito의 Then 단계
- ‘테스트 결과를 검증’하는 부분입니다.
- 예상되는 결과를 검증하고, 모의 객체의 메서드가 예상대로 호출되었는지를 확인할 수 있습니다.
💡 Given-When-Then 패턴 예시
- 해당 예시에서는 result 값과 expectedValue 값이 일치하는지를 검증하고 mockObject의 someMethod()가 예상대로 호출되었는지를 확인하기 위한 예시입니다.
1. Given : 사전 조건
- mockObject의 someMethod() 메서드가 호출되었을 때 expectedValue를 반환하도록 설정합니다.
2. When : 동작을 검증
- testedObject의 testedMethod()를 호출하여 실제로 테스트 대상 메서드를 실행합니다.
3. Then : 테스트 결과 검증
- result가 expectedValue와 일치하는지를 검증하고, mockObject의 someMethod()가 예상대로 호출되었는지를 확인합니다.
// Given
Mockito.when(mockObject.someMethod()).thenReturn(expectedValue);
// When
Object result = testedObject.testedMethod();
// Then
assertEquals(expectedValue, result);
Mockito.verify(mockObject).someMethod();
💡 [참고] Junit5의 Given-When-Then 패턴을 참고하시거나 BDD 개발 방법론의 패턴을 참고하시면 도움이 됩니다.
7) Mockito 초기 환경설정
💡 Mockito 초기 환경설정
- Mockito를 사용하기 위해 Gradle 내에 의존성을 추가하여 Service나 Repository 기반의 테스트 메서드를 구현하기 위한 환경설정에 대해 알아봅니다.
1. 의존성 설정
💡 의존성 설정
- 테스트하는 환경이 Java 11 버전을 사용하고 있기에 ’mockito-core’ 5.x.x 버전을 의존성으로 주입합니다.
(mockito-core의 버전에 따라 제공하는 Java 버전이 다르기에 하단의 확인해보셔야 합니다)
// [필수] mockito 의존성 추가
testImplementation 'org.mockito:mockito-core:5.8.0'
// [선택] spring-boot-starter-test를 사용하지 않는 경우 추가 필요
testImplementation 'org.mockito:mockito-junit-jupiter:5.8.0'
💡 [참고] 추가 라이브러리에 대한 설명
라이브러리 | 설명 |
mockito-core | 기본 Mocking 및 검증을 위한 핵심 Mockito 라이브러리입니다. |
mockito-all | Mockito의 모든 기능을 포함하고 있는 라이브러리입니다. (* 단 해당 버전은 2버전까지 해서 종료되었습니다.) |
mockito-inline | 인라인 Mocking을 위한 Mockito 라이브러리입니다. |
mockito-junit-jupiter | Mockito와 JUnit Jupiter를 함께 사용할 수 있도록 지원하는 라이브러리 입니다. (* 단 spring-boot-starter-test 라이브러리 내에 포함되어 있으니 이를 사용하지 않는 다면 설정이 필요합니다) |
[ 더 알아보기 ]
💡 mockito-core와 mockito-inline의 차이
- mockito-core는 기본적인 Mocking 및 검증을 위한 라이브러리이고, mockito-inline은 인라인 Mocking을 지원하여 테스트의 유연성과 편의성을 높여줍니다.
💡 Mockito 버전별 Java 버전 요구사항
Mockito 버전 | 요구 Java 버전 | 특징 |
Mockito 5.x | Java 11 이상 | |
Mockito 4.x | Java 8 이상 | deprecated |
Mockito 3.x | Java 8 이상 | |
Mockito 2.x | Java 8 이상 | |
Mockito 1.x | Java 5 이상 |
2. 서비스 테스트를 위한 파일 생성: Go to… - Test 선택
3. 테스트 생성
4. 테스트 생성 파일 확인
8) Mockito 단계 별 구성 과정 : Service 테스트
1. Mock 초기화
💡 Mock 초기화
- Mockito를 사용하여 Mock 객체를 초기화하는 데 사용됩니다.
- 이를 수행하면 테스트에서 Mock 객체를 사용할 수 있고 테스트를 실행할 때 예상된 동작을 가진 Mock 객체를 사용할 수 있습니다.
초기화 방식 | 사용 버전 | 설명 |
@ExtendWith(MockitoExtension.class) | JUnit 5 | - Mockito를 사용하여 테스트 클래스를 초기화하는 데 사용됩니다. - MockitoExtension은 JUnit 5의 확장 기능으로, Mockito의 기능을 테스트 클래스에 적용합니다. |
MockitoAnnotations.initMocks(this) | JUnit 4 | - Mockito를 사용하여 Mock 객체를 초기화하는 데 사용됩니다. 해당 메서드는 deprecated 되었습니다. -initMocks() 메서드는 목 객체를 주입하고 테스트 클래스의 필드에 할당합니다. 이 방법은 JUnit 4에서 사용됩니다. |
MockitoAnnotations.openMocks(this) | JUnit 5 | - Mockito 3.4.0 버전부터 도입된 새로운 초기화 방식입니다. - openMocks() 메서드는 목 객체를 주입하고 테스트 클래스의 필드에 할당합니다. 이 방법은 JUnit 5와 함께 사용됩니다. |
1.1. @ExtendWith(MockitoExtension.class)를 이용한 초기화 예시
💡 @ExtendWith(MockitoExtension.class)를 이용한 초기화 예시
@ExtendWith(MockitoExtension.class)
class CodeMapperTest {
@Mock
private CodeMapper codeMapper;
@InjectMocks
private CodeService codeService;
@BeforeEach
void setUp() {
}
}
1.2. MockitoAnnotations.initMocks(this)를 이용한 초기화 예시
💡 MockitoAnnotations.initMocks(this)를 이용한 초기화 예시
class CodeMapperTest {
@Mock
private CodeMapper codeMapper;
@InjectMocks
private CodeService codeService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
}
💡 [참고] MockitoAnnotations.initMocks(this)의 경우 deprecated 되어 MockitoAnnotations.openMocks(this);을 사용하기를 권장합니다.
2. Mock Annotation
어노테이션 | 사용 타입 | 설명 |
@Mock | Repository / Mapper | 모의 객체(Mock Object)를 생성하는데 사용됩니다. |
@InjectMocks | Service (implements) | 모의 객체를 생성하고 인스턴스를 주입하는데 사용됩니다. |
💡 Mock Annotation
- Service 부분을 테스트를 진행한다 가정을 하였을 때 @InjectMocks, @Mock를 활용하여 구성하였습니다.
1 @InjectMocks를 통해서 객체를 생성하고 의존성을 주입을 합니다.
2. @Mock를 통해 모의 객체를 구성을 하는데, 이유는 Service 로직 내에 Mapper로 인한 처리를 위해 모의 객체를 구성을 하여 null 오류를 방지합니다.
@ExtendWith(MockitoExtension.class)
class CodeMapperTest {
@Mock
private CodeMapper codeMapper;
@InjectMocks
private CodeService codeService;
@BeforeEach
void setUp() {
}
}
3. Mock @Test 구성
💡 Mock @Test 구성
- 테스트를 하려는 서비스의 메서드 별로 구성을 하였습니다.
@ExtendWith(MockitoExtension.class)
class CodeServiceTest {
@Mock
private CodeMapper codeMapper;
@InjectMocks
private CodeServiceImpl codeService;
@BeforeEach
void setUp() {
//
}
@AfterEach
void tearDown() {
//
}
@Test
void testCodeService() {
}
@Test
void selectCodeList() {
}
}
9) Mockito 예제: Service 테스트
1. Mockito를 통한 Service & Repository 테스트 요약
💡 Mockito를 통한 Service & Repository 테스트 요약
1. 해당 테스트를 위해서 Service 구현체와 모의 객체를 구성한 Mapper 간의 비교를 수행합니다.
2. 가상의 모의 객체의 결과와 Service의 구현체에서 구성한 값이 같으면 통과하게 됩니다.
💡 Service 주요 메서드와 코드
메서드 명 | 요청 | 설명 |
selectCodeList | CodeDTO | 코드 리스트를 조회합니다 |
selectCodeByCd | String cd | 키 값을 기반으로 코드 단건을 조회합니다 |
insertCode | CodeDTO | 코드를 등록합니다. |
updateCode | CodeDTO | 코드를 수정합니다 |
deleteCode | CodeDTO | 코드를 삭제합니다 |
💡 [예시] Service의 인터페이스
package com.adjh.multiflexapi.service;
import com.adjh.multiflexapi.model.CodeDto;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public interface CodeService {
// 코드 리스트 조회
List<CodeDto> selectCodeList(CodeDto codeDto);
// 코드 단건 조회
CodeDto selectCodeByCd(String cd);
// 코드 생성
int insertCode(CodeDto codeDto);
// 코드 수정
int updateCode(CodeDto codeDto);
// 코드 삭제
int deleteCode(CodeDto codeDto);
}
💡 [예시] Service의 인터페이스 구현체
package com.adjh.multiflexapi.service.impl;
import com.adjh.multiflexapi.common.codes.ErrorCode;
import com.adjh.multiflexapi.config.exception.BusinessExceptionHandler;
import com.adjh.multiflexapi.mapper.CodeMapper;
import com.adjh.multiflexapi.model.CodeDto;
import com.adjh.multiflexapi.service.CodeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
public class CodeServiceImpl implements CodeService {
private final SqlSession sqlSession;
@Autowired
public CodeServiceImpl(SqlSession ss) {
this.sqlSession = ss;
}
/**
* 코드 리스트를 조회합니다.
*
* @param codeDto CodeDto
* @return List<CodeDto>
*/
@Transactional(readOnly = true)
public List<CodeDto> selectCodeList(CodeDto codeDto) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
return cm.selectCodeList(codeDto);
}
/**
* 코드 키 값을 기반으로 코드 정보를 조회합니다
*
* @param cd String
* @return CodeDto
*/
@Transactional(readOnly = true)
public CodeDto selectCodeByCd(String cd) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
return cm.selectCodeByCd(cd);
}
/**
* 코드 리스트 조회
*
* @param codeDto 코드
* @return CodeDto
*/
@Transactional(readOnly = true)
public CodeDto selectCode(CodeDto codeDto) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
return cm.selectCode(codeDto);
}
/**
* 코드를 추가합니다.
*
* @param codeDto CodeDto
* @return int
*/
@Transactional
public int insertCode(CodeDto codeDto) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
try {
return cm.insertCode(codeDto);
} catch (Exception e) {
throw new BusinessExceptionHandler(e.getMessage(), ErrorCode.INSERT_ERROR);
}
}
/**
* 코드 삭제
*
* @param codeDto CodeDto
* @return int
*/
@Transactional
public int deleteCode(CodeDto codeDto) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
try {
return cm.deleteCode(codeDto);
} catch (Exception e) {
throw new BusinessExceptionHandler(e.getMessage(), ErrorCode.INSERT_ERROR);
}
}
/**
* 코드 수정 : 키 값 기반
*
* @param codeDto CodeDto
* @return int
*/
@Transactional
public int updateCode(CodeDto codeDto) {
try {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
// 1. 코드의 키값을 기반으로 데이터를 조회합니다.
CodeDto selectCodeByCd = cm.selectCodeByCd(codeDto.getCd());
// 3. 최종적으로 값을 수정합니다.
return cm.updateCode(selectCodeByCd);
} catch (Exception e) {
throw new BusinessExceptionHandler(e.getMessage(), ErrorCode.UPDATE_ERROR);
}
}
}
2. Controller 테스트 구조 및 사용 예시
2.1. Service(구현체) : 조회 테스트(요청 파라미터 전달)
💡 Service(구현체) : 조회 테스트(요청 파라미터 전달)
- 해당 테스트의 요점은 가상의 모의 객체를 만들어서 service에 처리된 결과를 비교하는 서버스를 테스트하기 위함입니다.
1. @ExtendWith(MockitoExtension.class)
- JUnit5와 Mockito를 함께 사용하기 위한 어노테이션을 선언합니다.
2. @Mock
- 모의 객체로 사용할 sqlSession와 codeMapper를 선언합니다.
3. @InjectMocks
- 실제 테스트를 수행할 ServiceImpl로 객체를 생성하고 의존성 주입을 하기 위해 어노테이션으로 선언합니다.
4. @Test
- 실제 처리되는 테스트가 수행되는 메서드입니다.
4.1. 가상의 객체를 구성합니다.
4.2. 가상의 codeMapper의 메서드를 호출하여 가상의 객체로 반환하도록 구성
4.3. 가상의 sqlSession에 Mapper를 지정해 줍니다.
(* 해당 부분은 서비스 구현체(ServiceImpl)에서 사용되는 데이터를 구성하였습니다)
4.4. 테스트 대상 메서드를 호출합니다.
4.5. 가상의 Mapper 값과 테스트 대상 메서드를 비교합니다.
/**
* CodeService 구현체를 테스트를 수행합니다.
*
* @author : lee
* @fileName : CodeServiceTest
* @since : 12/11/23
*/
@ExtendWith(MockitoExtension.class)
class CodeServiceTest {
@Mock
private SqlSession sqlSession;
@Mock
private CodeMapper codeMapper;
@InjectMocks
private CodeServiceImpl codeService;
@BeforeEach
void setUp() {
//
}
@Test
void testCodeService() {
// *************** given : 사전 조건 ******************
// 1. 가상의 객체를 구성합니다.
CodeDto mockCodeDto = CodeDto.builder().cd("java").build();
// *************** when : 동작 검증 ******************
// 2. 가상의 codeMapper의 메서드를 호출하여 가상의 객체로 반환하도록 구성
Mockito.when(codeMapper.selectCodeByCd("java")).thenReturn(mockCodeDto);
// 3. 가상의 sqlSession에 Mapper를 지정해줍니다. (* 해당 부분은 서비스 구현체(ServiceImpl)에서 사용되는 데이터를 구성하였습니다)
Mockito.when(sqlSession.getMapper(CodeMapper.class)).thenReturn(codeMapper);
// 4. 테스트 대상 메서드를 호출합니다.
CodeDto result = codeService.selectCodeByCd("java");
// *************** when : 결과 검증 ******************
// 5. 가상의 Mapper 값과 테스트 대상 메서드를 비교합니다.
Assertions.assertEquals(mockCodeDto, result);
}
}
💡 [실행 결과] 아래와 같은 형태로 테스트가 성공함을 확인하였습니다.
2.2. Service(구현체) : 조회 테스트(객체 전달)
💡 Service(구현체) : 조회 테스트(요청 파라미터 전달)
- 해당 테스트의 요점은 가상의 모의 객체를 만들어서 service에 처리된 결과를 비교하는 서버스를 테스트하기 위함입니다.
1. @ExtendWith(MockitoExtension.class)
- JUnit5와 Mockito를 함께 사용하기 위한 어노테이션을 선언합니다.
2. @Mock
- 모의 객체로 사용할 sqlSession와 codeMapper를 선언합니다.
3. @InjectMocks
- 실제 테스트를 수행할 ServiceImpl로 객체를 생성하고 의존성 주입을 하기 위해 어노테이션으로 선언합니다.
4. @Test
- 실제 처리되는 테스트가 수행되는 메서드입니다.
1. 가상의 객체를 구성합니다.
2. Service와 비교할 반환값을 구성합니다.
3. Mapper에 가상의 객체를 넣고 반환되는 값을 expectedCodeList 값으로 지정합니다.
4. 가상의 sqlSession에 Mapper를 지정해 줍니다.
(* 해당 부분은 서비스 구현체(ServiceImpl)에서 사용되는 데이터를 구성하였습니다)
5. 테스트 대상 메서드를 호출합니다.
6. 반환된 코드 리스트가 예상한 값과 일치하는지 검증합니다.
/**
* CodeService 구현체를 테스트를 수행합니다.
*
* @author : lee
* @fileName : CodeServiceTest
* @since : 12/11/23
*/
@ExtendWith(MockitoExtension.class)
class CodeServiceTest {
@Mock
private SqlSession sqlSession;
@Mock
private CodeMapper codeMapper;
@InjectMocks
private CodeServiceImpl codeService;
@BeforeEach
void setUp() {
//
}
@Test
@DisplayName("객체 값을 기반으로 여러개의 코드 값들을 조회합니다.")
void selectCodeList() {
// *************** given : 사전 조건 ******************
// 1. 가상의 객체를 구성합니다.
CodeDto mockCodeDto = CodeDto.builder().cd("java").build();
// 2. Service와 비교할 반환값을 구성합니다.
List<CodeDto> expectedCodeList = new ArrayList<>();
// *************** when : 동작 검증 ******************
// 3. 가상의 codeMapper의 메서드를 호출하여 가상의 객체로 반환되도록 지정합니다.
Mockito.when(codeMapper.selectCodeList(mockCodeDto)).thenReturn(expectedCodeList);
// 4. 가상의 sqlSession에 Mapper를 지정해줍니다. (* 해당 부분은 서비스 구현체(ServiceImpl)에서 사용되는 데이터를 구성하였습니다)
Mockito.when(sqlSession.getMapper(CodeMapper.class)).thenReturn(codeMapper);
// 5. 테스트 대상 메서드를 호출합니다.
List<CodeDto> actualCodeList = codeService.selectCodeList(mockCodeDto);
// *************** when : 결과 검증 ******************
// 6. 반환된 코드 리스트가 예상한 값과 일치하는지 검증합니다.
Assertions.assertEquals(expectedCodeList, actualCodeList);
}
}
💡 [실행 결과] 아래와 같은 형태로 테스트가 성공함을 확인하였습니다.
💡 [참고] Spring Boot 환경에서 JUnit5를 이용하기 위한 관련 글들입니다.
분류 | URL |
JUnit 5 이론 및 구성 요소 | https://adjh54.tistory.com/341 |
JUnit 5 환경구성 및 활용예제 | https://adjh54.tistory.com/342 |
JUnit 5 + Mockito 이론 및 활용예제 | https://adjh54.tistory.com/346 |
JUnit 5 + MockMvc 이론 및 활용예제 | https://adjh54.tistory.com/347 |
Assertions API Document | https://adjh54.tistory.com/348 |
오늘도 감사합니다. 😀
반응형
'Java > Testing' 카테고리의 다른 글
[Java] Spring Boot JUnit5 이해하기 -3: @RepeatedTest, @ParameterizedTest를 이용한 반복 테스트 (0) | 2024.07.28 |
---|---|
[Java] Spring Boot MockMvc 이해하기 : 테스트 흐름 및 사용예제 (0) | 2023.12.15 |
[Java] Spring Boot JUnit5 이해하기 -2 : 환경구성 및 활용 예제 (0) | 2023.12.08 |
[Java] Spring Boot JUnit5 이해하기 -1 : 이론 및 구조 (1) | 2023.12.07 |