스프링부트 프로젝트 생성시 spring-boot-starter-test에서 자동으로 Mockito를 추가해 준다.
테스트할 때 db혹은 외부 api등을 항상 사용하면서 테스트할 수 없다.
그런 경우 mock으로 만들어 어떻게 동작할지, 어떤 응답이 올지 가정해서 테스트를 할 수 있다.
mock을 사용한다면 repository가 구현이 안되어있어도, 외부 서비스 없이도 테스트를 만들수 있다.
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
public interface MemberService {
Optional<Member> findById(Long memberId);
void validate(Long memberId);
}
public interface StudyRepository extends JpaRepository<Study, Long> {
}
public class StudyService {
private final MemberService memberService;
private final StudyRepository repository;
public StudyService(MemberService memberService, StudyRepository repository) {
assert memberService != null;
assert repository != null;
this.memberService = memberService;
this.repository = repository;
}
public Study createNewStudy(Long memberId, Study study) {
Optional<Member> member = memberService.findById(memberId);
study.setOwner(member.orElseThrow(() -> new IllegalArgumentException("Member doesn't exist for id: '" + memberId + "'"));
return repository.save(study);
}
}
MemberService memberService = mock(MemberService.class);
StudyRepository studyRepository = mock(StudyRepository.class);
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
@Mock
MemberService memberService;
@Mock
StudyRepository studyRepository;
@Test
void createStudyService(@Mock MemberService memberService,
@Mock StudyRepository studyRepository) {
StudyService studyService = new StudyService(memberService, studyRepository);
assertNotNull(studyService);
}
기본 Mock 객체의 행동
Null을 리턴한다. (Optional 타입은 Optional.empty 리턴)
Primitive 타입은 기본 Primitive 값.
콜렉션은 비어있는 콜렉션.
Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않는다.
Stubbing이란 Mock 객체를 조작해서
- 특정한 값을 리턴하거나 예외를 던지도록 만들 수 있다.
when([메소드]).thenReturn([리턴값])
when([메소드]).thenThrow([익셉션])
doThrow([익셉션]).when([클래스]).[메소드]
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
@Test
void createStudyService(@Mock MemberService memberService,
@Mock StudyRepository studyRepository) {
StudyService studyService = new StudyService(memberService, studyRepository);
assertNotNull(studyService);
Member member = new Member();
member.setId(1L);
member.setEmail("abc@gmail.com");
//return 값이 있는 경우
when(memberService.findById(1L)).thenReturn(Optional.of(member));
//when(memberService.findById(2L)).thenReturn(Optional.of(member));
//when(memberService.findById(any())).thenReturn(Optional.of(member));
//when(memberService.findById(1L).thenThrow(new RuntimeException());
Optional<Member> findById = memberService.findById(1L);
assertEquals("abc@gmail.com", findById.get().getEmail());
//void타입
doThrow(new IllegalArgumentException()).when(memberService).validate(1L);
assertThatThrownBy(() -> memberService.validate(1L)).isInstanceOf(IllegalArgumentException.class);
}
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
@Test
void createStudyService(@Mock MemberService memberService,
@Mock StudyRepository studyRepository) {
//given
StudyService studyService = new StudyService(memberService, studyRepository);
assertNotNull(studyService);
Member member = new Member();
member.setId(1L);
member.setEmail("abc@gmail.com");
Study study = new Study(10, "java");
when(memberService.findById(1L)).thenReturn(Optional.of(member));
//when
studyService.createNewStudy(1L, study);
//then
assertEquals(member, study.getOwner());
verify(repository).save(study);
//verify -> 메서드가 실제로 호출되었는지 검증
}
BDD: 애플리케이션이 어떻게 “행동”해야 하는지에 대한 공통된 이해를 구성하는 방법으로, TDD에서 창안했다.
Mockito는 BddMockito라는 클래스를 통해 BDD 스타일의 API를 제공한다.
When -> Given
when(memberService.findById(1L)).thenReturn(Optional.of(member));
-> given(memberService.findById(1L)).willReturn(Optional.of(member));
when(memberService.findById(1L).thenThrow(new RuntimeException());
-> given(memberService.findById(1L)).willThrow(new RuntimeException());
//void
doThrow(new IllegalArgumentException()).when(memberService).validate(1L);
-> willThrow(new IllegalArgumentException()).given(memberService).validate(1L);
Verify -> Then
verify(repository).save(study);
-> then(repository).should().save(study);