킹의 개발일지
JUnit, Mockito 및 MockMvc를 사용한 스프링 부트 단위 테스트(4) 본문
Mocking with Mockito
서비스 레이어는 주로 관련된 DAO 클래스를 주입받아 사용하는 경우가 많다.
때문에 테스트를 하려면 서비스 레이어가 의존하는 DAO 객체, DB 등 테스트하고자 하는 대상 이외의 것들도 함께 고민해야 할것이다.
원하는 대상만 고립시켜 최소한의 의존으로 테스트 하는 방법은 없을까.
이때 리허설에서 최소한의 장비만으로 시범 무대를 가지는것처럼
서비스 레이어가 의존하는 실제 DAO를 사용하지 말고 "이러한 요청이 있으면 이런 값을 반환하세요~ " 라고 미리 말해둔 대역을 쓰면 된다. 이를 Mocking이라고한다.

Mocking을 하면 다음과 같은 장점이 있다.
- 테스트 하고자하는 클래스를 격리시켜 해당 클래스만 테스트 할 수 있다.
- 특정 메서드가 호출되면 DAO가 특정값을 반환하도록 설정(실제 DB 연결 없이도)
- 토큰이 없는 상황에서도 가짜 REST API를 설정해서 특정 값을 반환하도록 설정할 수 있다.
Mock 테스트를 할 때 다음과 같은 과정으로 진행한다.

- Setup: '내가 ~하면 너는 ~ 반환해줘!' 라는것을 설정한다.
- Execute: 테스트 하고자하는 메서드를 호출한다.
- Assert: 메서드 호출 결과가 원하는 결과와 맞는지 확인한다.
- Verify: 선택사항이며 메서드 호출에 관한것들을 확인한다. 예를 들어 이 메서드가 테스트중 몇번 호출됐는가.., Mock 객체는 메서드가 호출될 때 상황을 감지하고 있다.
여러 Mocking 프레임워크 중 스프링부트는 Mockito를 빌트인으로 제공한다.
테스트 해보고자 하는 시나리오는 다음과 같다. ApplicationService는 ApplicationDao에 의존한다.
때문에 ApplicationService 메서드를 테스트하고자 할 때도 ApplicationDao가 필요한 상황이다.
이때 Mocking을 사용해보자.
public class ApplicationService {
@Autowired
private ApplicationDao applicationDao;
public double addGradeResultsForSingleClass(List<Double> numbers) {
return applicationDao.addGradeResultsForSingleClass(numbers);
}
public double findGradePointAverage (List<Double> grades ) {
return applicationDao.findGradePointAverage(grades);
}
public Object checkNull(Object obj) {
return applicationDao.checkNull(obj);
}
}
@Mock & @InjectMocks
우선 다음과 같이 대역으로 쓸 타입에 @Mock 어노테이션을 붙혀주자.
그리고 테스트 하고자하는 객체에 @InjectMocks 어노테이션을 붙혀주자.
@Mock
private ApplicationDao applicationDao;
@InjectMocks
private ApplicationService applicationService;
- ApplicationService 는 ApplicationDao 의존을 가지고 있다. 따라서 ApplicationService 의 메서드를 사용하면 ApplicationDao 가 위임돼 호출 될것이다.
- 때문에 @Mock으로 목 객체를 만들고 목 객체를 의존으로 갖는 ApplicationService 객체에게 @InjectMocks를 통해서 의존을 주입해주는것이다.
- 즉, ApplicationDao 객체의 메서드에 Mocking으로 반환값을 정해두고, ApplicationService 객체의 메서드를 호출하면 설정해둔 반환값을 바탕으로 ApplicationService 객체의 메서드가 호출 될것이다.
이후 다음과 같이 테스트 할 수 있다.
@BeforeEach
public void beforeEach() {
studentOne.setFirstname("Juhong");
studentOne.setLastname("An");
studentOne.setEmailAddress("test@test.com");
studentOne.setStudentGrades(studentGrades);
}
@DisplayName("When & Verify")
@Test
public void assertEqualsTestAddGrades(){
when(applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()))
.thenReturn(100.0);
// 셋업해둔 applicationDao의 addGradeResultsForSingleClass() 메서드가 100.0을 반환한다.
assertEquals(100, applicationService.addGradeResultsForSingleClass(
studentOne.getStudentGrades().getMathGradeResults()));
// applicationDao의 addGradeResultsForSingleClass() 메서드가 호출됐는지 확인.
verify(applicationDao).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
// applicationDao의 addGradeResultsForSingleClass() 메서드가 몇번 호출됐는지 확인
// Mock객체는 메서드 호출 횟수를 감지하고 있다.
verify(applicationDao, times(1)).addGradeResultsForSingleClass(
studentGrades.getMathGradeResults());
}
MockBean
Mockito프레임워크의 @Mock & @InjectMocks 대신
스프링부트는 @MockBean, @Autowired를 지원한다.(org.springframework.boot.test.mock.mockito.MockBean)
@MockBean은 Mockito의 @Mock 기능을 포함하면서 Mock 빈을 application context에 추가해준다.
@MockBean 어노테이션을 붙힌 빈이 컨테이너에 있다면 Mock 빈은 그걸로 자동으로 대체할 것이다. 그래서 목빈은
@Autowired 를 통해서 의존주입을 할 수 있게 된다.
@MockBean
private ApplicationDao applicationDao;
@Autowired
private ApplicationService applicationService;
@MockBean으로 대체 하더라도 기존 테스트는 문제없이 성공한다.
@SpringBootTest(classes = MvcTestingExampleApplication.class)
public class MockAnnotationTest {
@Autowired
ApplicationContext applicationContext;
@Autowired
CollegeStudent studentOne;
@Autowired
StudentGrades studentGrades;
// @MockBean 대체
// @Mock
// private ApplicationDao applicationDao;
//
// @InjectMocks
// private ApplicationService applicationService;
@MockBean
private ApplicationDao applicationDao;
@Autowired
private ApplicationService applicationService;
@BeforeEach
public void beforeEach() {
studentOne.setFirstname("Juhong");
studentOne.setLastname("An");
studentOne.setEmailAddress("test@test.com");
studentOne.setStudentGrades(studentGrades);
}
@DisplayName("When & Verify")
@Test
public void assertEqualsTestAddGrades(){
when(applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()))
.thenReturn(100.0);
// 셋업해둔 applicationDao의 addGradeResultsForSingleClass() 메서드가 100.0을 반환한다.
assertEquals(100, applicationService.addGradeResultsForSingleClass(
studentOne.getStudentGrades().getMathGradeResults()));
// applicationDao의 addGradeResultsForSingleClass() 메서드가 호출됐는지 확인.
verify(applicationDao).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
// applicationDao의 addGradeResultsForSingleClass() 메서드가 몇번 호출됐는지 확인
// Mock객체는 메서드 호출 횟수를 감지하고 있다.
verify(applicationDao, times(1)).addGradeResultsForSingleClass(
studentGrades.getMathGradeResults());
}
@DisplayName("Find Gpa")
@Test
public void assertEqualsTestFindGpa() {
when(applicationDao.findGradePointAverage(studentGrades.getMathGradeResults()))
.thenReturn(88.31);
assertEquals(88.31, applicationService.findGradePointAverage(
studentOne.getStudentGrades().getMathGradeResults()));
}
@DisplayName("Not Null")
@Test
public void testAssertNotNull() {
when(applicationDao.checkNull(studentGrades.getMathGradeResults()))
.thenReturn(true);
assertNotNull(applicationService.checkNull(studentOne.getStudentGrades().getMathGradeResults())
,"Object should not be null");
}
}
Throwing Exception
Mock 객체가 예외를 던지도록 할 떄가 있다.
예를 들어 코드가 던져진 예외를 어떻게 처리할지 테스트하기 위해 Mock객체가 의도적으로 예외를 던지록 할 수 있다.
단발적인 예외의 경우 다음과 같이 테스트를 작성할 수 있다.
@DisplayName("Throw runtime exception")
@Test
public void throwRuntimeException() {
CollegeStudent nullStudent = (CollegeStudent) applicationContext.getBean("collegeStudent");
// 단발적인 예외를 던지려면 doThrow로 셋업한다.
doThrow(new RuntimeException()).when(applicationDao).checkNull(nullStudent);
assertThrows(RuntimeException.class, ()-> applicationService.checkNull(nullStudent));
verify(applicationDao, times(1)).checkNull(nullStudent);
}
지금까지 스프링 부트가 제공하는 Mockito를 사용해서 테스트를 작성해 보았다.
단순한 예제이지만 Mocking 한다는것이 어떤 흐름으로 흘러가는지를 알 수 있었다.
역시 여러번 써보면서 공부해야 빨리 배울수 있을것 같다.
'Junit' 카테고리의 다른 글
| JUnit, Mockito 및 MockMvc를 사용한 스프링 부트 단위 테스트(5) (0) | 2022.06.17 |
|---|---|
| JUnit, Mockito 및 MockMvc를 사용한 스프링 부트 단위 테스트(3) (0) | 2022.06.16 |
| JUnit, Mockito 및 MockMvc를 사용한 스프링 부트 단위 테스트(2) (0) | 2022.06.10 |
| JUnit, Mockito 및 MockMvc를 사용한 스프링 부트 단위 테스트(1) (0) | 2022.06.03 |
| JUnit, Mockito 및 MockMvc를 사용한 스프링 부트 단위 테스트 (0) | 2022.06.01 |