💡 Mockito - 단위 테스트를 위해 모의 객체를 생성하고 관리하는 데 사용되는 Java 오픈소스 프레임워크를 의미합니다. - 이를 사용하면 실제 객체의 동작을 모방하는 모의 객체(Mock Object)를 생성하여 코드의 ‘특정 부분을 격리’시키고 테스트하기 쉽게 만들어줍니다.
- 주로 단일 컴포넌트의 동작을 테스트하는 데 사용되며 클래스 내의 개별 메서드나 함수, 서로 다른 클래스 또는 컴포넌트 간의 상호작용, 객체들 간의 협업 등을 테스트할 수 있습니다.
[ 더 알아보기 ] 💡모의 객체(Mock Object)
- 실제 사용되는 객체 생성을 대체하기 위해 테스트에 사용되는 객체를 의미합니다. - 일반적으로 모의 객체의 변수 값은 null, 0, false와 같은 기본 타입의 값이 반환되며 메서드는 기본적으로 null을 구성이 됩니다.
- 하단의 그림은 Mockito를 사용하지 않고 JUnit5만을 사용하여서 서비스를 테스트한 테스트 케이스의 흐름을 보여줍니다. - 이러한 흐름은 @Test를 수행하는 testsIsNotNullCodeList()라는 메서드에서는 서비스 호출을 위해 ‘Local Server’를 실행시켜서 DB 데이터를 조회해 옵니다. - 해당 흐름에서는 매번 데이터 조회를 해오기 위해 ‘로컬 서버’를 실행시켜 테스트를 수행하는 데에 불편함이 발생하고 이를 대처하여 Mockito를 사용합니다.
💡 JUnit 5 + Mockito 흐름 - 하단의 그림은 Mockito를 사용하여 서비스를 테스트한 테스트 케이스의 흐름을 보여줍니다.
- 이러한 흐름은 @Test를 수행하는 TestMethod()라는 메서드에서 서비스 호출을 위해 getCodeList() 함수를 호출하지만 DB에서 데이터를 조회해오지 않는 형태입니다. - 이 과정에서는 JUnit5만을 이용하였던 방식과 다르게 직접적인 DB 호출을 수행하지 않고 Mock Object라는 모의 객체를 구성하여서 테스트를 진행하는 방식입니다.
- 모의 객체 생성(Mock) → 메서드 호출 예상 동작 설정(Stub) → 메서드 호출 검증(Verify)
1. 모의 객체 생성 : Mock
- Mockito를 사용하여 테스트에 필요한 '모의(가짜) 리스트'를 생성합니다. - MockList의 경우는 실제 리스트와 동일한 방식으로 동작을 하지만, 이 동작을 사전에 정의 할 수 있습니다.
2. 메서드 호출 예상 동작 설정 : Stub
- 모의 객체의 메서드 호출에 대한 ‘예상 동작’을 정의합니다. - 예상 동작으로 MockList에서 size() 메서드가 호출이 되었을 경우, 5를 반환하도록 지정하였습니다. - 그리고 하단에 실제 mockList.size()를 호출 하였을때 5의 값이 추출되었습니다.
3. 메서드 호출 검증 : Verify
- 모의 객체에 대해 특정 메서드가 호출되고 예상된 인자와 함께 호출되었는지를 검증하는 메서드를 제공합니다. - 해당 경우는 mockList 내에 .size() 메서드가 호출이 되었는지 검증을 수행합니다.
@Test@DisplayName("기본 요소를 테스트 해봅니다.")voidtestSimple() {
// 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();
}
💡 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를 반환
- 모의 객체의 생성을 통해 실제 객체의 동작을 모방하는 모의 객체를 만드는 과정을 의미합니다.
- 이러한 모의객체는 테스트 중인 코드를 격리하고 다양한 시나리오를 시뮬레이션하는 데 사용됩니다.
// 예시 클래스publicclassExampleClass {
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();
- @InjectMocks, @Mock는 Mockito에서 ‘유닛 테스트’에 사용되고 @MockBean는 Spring Boot에서 ‘통합 테스트’에서 사용이 됩니다.@Mock는 특정 클래스나 인터페이스에 대해 ‘모의 객체를 생성’하는 역할을 수행합니다. - @InjectMocks는 테스트 대상 객체에 ‘모의 객체를 주입’하는 역할을 수행합니다. 💡 모의 객체를 생성하는 것과 모의 객체를 주입한다의 차이는 무엇인가?
- @Mock와 같이 ‘모의 객체를 생성’한다는 것은 실제 객체와 동일한 메서드와 동작을 가지지만 실제 데이터나 외부 리소스와의 상호작용은 없습니다. - @InjectMocks와 같이 ‘모의 객체를 주입’하는다는 것은 테스트의 대상이 특정 모의 객체를 사용해야 할 때, 그 모의 객체를 자동으로 주입하여 테스트를 수행할 수 있도록 합니다. - 모의 객체를 주입하는 것은 @Mock로 생성한 모의 객체가 자동으로 주입되어 테스트가 진행됩니다.
4. Verifying no more interactions - 추가적인 상호작용이 없는 것을 확인하는 부분입니다. mockClass와 관련하여 추가적인 상호작용이 없는지 확인합니다.
@TestvoidtestSimple2() {
// 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);
}
- 테스트하는 환경이 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을 지원하여 테스트의 유연성과 편의성을 높여줍니다.
- 해당 테스트의 요점은 가상의 모의 객체를 만들어서 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)classCodeServiceTest {
@Mockprivate SqlSession sqlSession;
@Mockprivate CodeMapper codeMapper;
@InjectMocksprivate CodeServiceImpl codeService;
@BeforeEachvoidsetUp() {
//
}
@TestvoidtestCodeService() {
// *************** given : 사전 조건 ******************// 1. 가상의 객체를 구성합니다.CodeDtomockCodeDto= 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. 테스트 대상 메서드를 호출합니다.CodeDtoresult= 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)classCodeServiceTest {
@Mockprivate SqlSession sqlSession;
@Mockprivate CodeMapper codeMapper;
@InjectMocksprivate CodeServiceImpl codeService;
@BeforeEachvoidsetUp() {
//
}
@Test@DisplayName("객체 값을 기반으로 여러개의 코드 값들을 조회합니다.")voidselectCodeList() {
// *************** given : 사전 조건 ******************// 1. 가상의 객체를 구성합니다.CodeDtomockCodeDto= CodeDto.builder().cd("java").build();
// 2. Service와 비교할 반환값을 구성합니다.
List<CodeDto> expectedCodeList = newArrayList<>();
// *************** 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);
}
}