이번에 얕은 mockito지식을 가지고 테스트를 하다가, 엄청난 삽질을 했다.. 반성합니다.
내가 한 삽질과 mockito 프레임워크 내용을 정리할 겸 포스팅을 하게되었다.
@Mock, @Spy, @MockBean, @SpyBean 와 관련되 내용과 내가 삽질을 한 이유를 적어보겠다.
일단, Mockito 어노테이션의 의미를 알아보자.
Mocito 어노테이션은 아래와 같은 것들이 존재한다.
@Mock
@MockBean
@SpyBean
@InjectMocks
@Mock : mock 객체를 만들어 반환(실제 인스턴스 없이 가상의 mock 인스턴스를 직접 만들어 사용)
@Spy : spy 객체를 만들어 반환(실제 인스턴스를 사용해서 mocking 함, Spy 객체는 행위를 지정하지 않으면 객체를 만들 때 사용한 실제 인스턴스의 메서드를 호출한다.)
@InjectMocks : @Mock이나 @Spy 객체를 자신의 멤버 클래스와 일치하면 주입
@MockBean : ApplicationContext에 mock객체를 추가
@SpyBean : ApplicationContext에 spy객체를 추가
2019/11/18 - [프로그래밍 노트/Junit] - Mockito 어노테이션(@Mock, @InjectMocks)
2019/11/13 - [프로그래밍 노트/Junit] - 모키토 프레임워크(Mockito framework)
@MockBean vs @Mock
@MockBean은 Mock과 달리 org.springframework.boot.test.mock.mockito 패키지 하위에 존재한다. spring-boot-test 에서 제공하는 어노테이션인데, Mockito의 Mock객체들을 Spring의 ApplicationContext에 넣어준다. 그리고 동일한 타입이 존재할 경우 MockBean으로 교체해준다.
Test 도중 Spring에서 어느 의존성도 필요하지 않다면, Mockito의 @Mock을 사용하면 되지만, Spring Container가 관리하는 bean들 중 하나이상을 Mocking하고 싶다면 @MockBean을 사용하면 된다.
삽질 노트
항상 의존성이 없는 로직만을 테스트를 했기 때문에, @Mock과 @InjectMock을 사용해도 이슈가 없었다.
그러나 여러가지 의존성을 가지는 Serivce를 테스트 하려다보니 내 뜻대로 되지 않았다.
비슷한 상황을 만들기 위해서, 괴상한 코드를 만들었다.
나는 PersonService.saveUser()를 테스트하고싶다.
PeronService.java는 아래와 같다.
@Service
@RequiredArgsConstructor
public class PersonService {
private final PersonApiService apiService;
private final CityRepository cityRepository;
private final PersonRepository personRepository;
public Optional<Person> savePerson(char firstChar){
// 1. api에서 Person List 가져옴
List<Person> personList = apiService.getPersonListByFirstChar(firstChar);
if(personList.isEmpty()) return Optional.empty();
Optional<Person> personOptional = personList.stream().max(Comparator.comparingDouble(Person::getHeight));
if(!personOptional.isPresent()){
throw new RuntimeException("API 오류");
}
// 2. person에 저정된 citySeq로 countryName을 가져옴
Person person = personOptional.get();
String countryName = cityRepository.getCountryNameByCitySeq(person.getCitySeq());
person.setCountryName(countryName);
// 3. person 저장
return Optional.of(personRepository.savePerson(person));
}
}
자세히 볼필요없다. PersonService는 여러가지 의존성을 갖고있다. PersonApiService, CitryRepository, PersonRepository
현재 PersonApiService.getPersonListByFirstChar 가 미완성인 상태라고 가정하자.
내가 이때 생각한 단순한 생각은 "아 PersonApiService의 getPersonListByFirstChar 를 목킹하자" 였다.
테스트 코드를 작성하였다.
@SpringBootTest
class PersonTest2 {
@Mock
private PersonApiService personApiService;
@InjectMocks
private PersonService personService;
@Test
void test(){
when(personApiService.getPersonListByFirstChar('c')).thenReturn(Collections.singletonList(Person.builder().name("corn").citySeq(4L).build()));
personService.savePerson('c');
}
}
오류를 만난다. java.lang.NullPointerException
그렇다.. PersonService @InjectMocks으로 정의되어있기 때문에 의존관계에 있는 객체들이 모두 null 이였다. (당연히 mocking하지 않은 객체들은 자동 주입이 되겠지? 라고 왜 생각하고 있었을까)
그렇다면 PersonSerivce와 의존관계가 있는 객체들을 모두 @Mock객체로 만든 후 행위를 정의해 주어야하는 것인가..?
@SpringBootTest
class PersonTest2 {
@Mock
private PersonApiService personApiService;
@Mock
private CityRepository cityRepository;
@Mock
private CityRepository pesonRepository;
@InjectMocks
private PersonService personService;
@Test
void test(){
}
}
아닌거 같다.. 실제 메소드에는 더 많은 메소드들이 존재했다. 일일이 행위를 정의하는 건 바보같은 짓이다.
구글링을 하였다. 다행히 해결법이 있었다.
@SpringBootTest
@Slf4j
class PersonTest {
@Autowired
private PersonService personService;
@MockBean
private PersonApiService apiService;
@Test
void savePersonTest(){
when(apiService.getPersonListByFirstChar('c')).thenReturn(Arrays.asList());
personService.savePerson('c');
}
}
@Autowired + @MockBean, @SpyBean 조합을 사용하는 것이다.
=> @MockBean 이나 @SpyBean은 Mock 객체를 스프링 컨텍스트에 등록한다.
=> @Autowired 어노테이션이 달린 PersonApiService은 의존성 있는 객체들이 자동 주입된다. (Mock객체를 주입받는다.)
이렇게 되면 의존성 있는 객체들의 행위를 일일이 정의하지 않아도 되며, 모킹할 객체만 지정해주면된다. 해당 내용은 어플리케이션컨텍스트를 올려서 빈들을 사용하는 케이스이다. @InjectMock과 @Mock은 어플리케이션컨텍스트를 사용하지 않는다. 따라서 일일이 정의를 해줘야 했던 것이다......기계적으로 @InjectMock과 @Mock만 쓰다보니..^^;
'프로그래밍 노트 > TEST를 해보자' 카테고리의 다른 글
[Kotest] kotest, mockk 사용하기 (0) | 2023.10.25 |
---|---|
[Mockito] @Spy, @SpyMock 스터빙(stubbing)(thenReturn vs doReturn) (0) | 2020.12.14 |
Mockito 어노테이션(@Mock, @InjectMocks) (0) | 2019.11.18 |
모키토 프레임워크(Mockito framework) (0) | 2019.11.13 |
Hmacrest 프레임워크 사용하기_2 (0) | 2019.11.13 |