Mock: 진짜 객체와 비슷하게 동작하지만 프로그래머가 직접 그 객체의 행동을 관리하는 객체
Mockito: Mock 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법 제공
테스트를 작성하는 자바 개발자 50%+ 사용하는 Mock 프레임워크
스프링 부터 2.2+ 프로젝트 생성시 spring-boot-starter-test에서 자동으로 Mockito 추가해줌
if, 스프링 부트를 쓰지 않는다면 의존성을 직접 추가
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
다음 세 가지만 알면 Mock을 활용한 테스트를 쉽게 작성할 수 있음
Mockito.mock() 메소드로 만드는 방법
MemberService memberService= mock(MemberService.class);
StudyRepository studyRepository= mock(StudyRepository.class);
@Mock 어노테이션으로 만드는 방법
//mock 객체를 여러 테스트에 걸쳐 사용하는 경우
@ExtendWith(MockitoExtension.class)
class StudyServiceTest{
@Mock
MemberService memberService;
@Mock
StudyRepository studyRepository
}
//mock 객체를 특정 메소드에서만 만들고 싶은 경우
@ExtendWith(MockitoExtension.class)
class StudyServiceTest{
@Test
void createStudyService(@Mock MemberService memberService,
@Mock StudyRepository studyRepository){
StudyService studyService= new StudyService(memberService,studyRepository);
assertNotNull(studyService);
}
}
모든 Mock 객체의 행동
Mock 객체 조작
조작을 통해 특정 매개변수를 받은 경우 특정한 값을 리턴하거나 예외를 던지도록 만들 수 있음
when(memberService.findById(any())).thenReturn(Optional.of(member));
when(memberService.findById(1L)).thenThrow(new RuntimeException());
Void 메소드가 특정 매개변수를 받거나 호출된 경우 예외를 발생 시킬 수 있음
doThrow(new IllegalArgumentException()).when(memberService).validate(1L);
메소드가 동일한 매개변수로 여러번 호출될 때 각기 다른 행동을 하도록 조작할 수 있음
when(memberService.findById(any()))
.thenReturn(Optional.of(member))
.thenThrow(new RuntimeException())
.thenReturn(Optional.empty());
연습
Study study= new Study(10,"테스트");
// TODO memberService 객체에 findById 메소드를 1L 값으로 호출하면 Optional.of(member) 객체를 리턴하도록 Stubbing
when(memberService.findById(1L)).thenReturn(Optional.of(member));
// TODO studyRepository 객체에 save 메소드를 study 객체로 호출하면 study 객체 그대로 리턴하도록 Stubbing
when(studyRepository.save(study)).thenReturn(study);
studyService.createNewStudy(1L,study);
assertNotNull(study.getOwner());
assertEquals(member,study.getOwner());
Mock 객체가 어떻게 사용이 됐는지 확인할 수 있음
특정 메소드가 특정 매개변수로 몇번 호출 되었는지, 최소 한번은 호출 되었는지, 전혀 호출되지 않았는지
verify(memberService,times(1)).notify(study); //memberService에서 한번 study를 매개변수로 notify()가 호출이 됐어야 한다.
verify(memberService, never()).validate(any()); //validate는 어떠한 argument를 가지더라도 호출이 되지 않는다.
// 그렇지 않을 시 에러 발생!!
어떤 순서로 호출했는지
Inorder inOrder=inOrder(memberService);
inOrder.verify(memberService).notify(study);
inOrder.verify(memberService).notify(member);
//해당 매개변수를 순서로 메소드가 호출되어야 함
특정 시간 이내에 호출됐는지
특정 시점 이후 아무 일도 벌어지지 않았는지
verifyNoMoreInteractions(memberService);
BDD: 어플리케이션이 어떻게 행동해야 하는지에 대한 공통된 이해를 구성하는 방법으로 TDD에서 창안
행동에 대한 스팩
Mockito는 BddMockito라는 클래스를 통해 BDD 스타일의 API를 제공
@Test
void createNewStudy(){
//Given
StudyService studyService= new StudyService(memberService,studyRepositody);
assertNotNull(studyService);
Member member=new Member();
member.setId(1L);
member.serEmail("dd@gmail.com");
Study study= new Study(10,"테스트");
//Given안에 When -> Given로
when(memberService.findById(1L)).thenReturn(Optional.of(member));
when(studyRepository.save(study)).thenReturn(study);
//When
studyService.createNewStudy(1L,study);
//Then
assertEquals(member,study.getOwner());
//Then안에 Verify -> Then로
verify(memberService,times(1)).notify(study);
verifyNoMoreInteraction(memberService);
}
위 코드를 토대로 아래처럼 알맞게 바꾸면 된다.
When->Given
given(memberService.findById(1L)).willReturn(Optional.of(member));
given(studyRepository.save(study)).willReturn(study);
Verify->Then
then(memberService).should(times(1)).notify(study);
then(memberService).shouldHaveNoMoreInteractions();
StudyService.java
public Study openStudy(Study study){
study.open();
Study openedStudy = repository.save(study);
memberService.notify(openedStudy);
return openedStudy;
}
StudyServiceTest.java
@DisplayName("다른 사용자가 볼 수 있도록 스터디를 공개한다.")
@Test
void openStudy() {
// Given
StudyService studyService = new StudyService(memberService, studyRepository);
Study study = new Study(10, "더 자바, 테스트");
// TODO studyRepository Mock 객체의 save 메소드를호출 시 study를 리턴하도록 만들기.
given(studyREpository.save(study)).willReturn(study);
// When
studyService.openStudy(study);
// Then
// TODO study의 status가 OPENED로 변경됐는지 확인
assertEquals(StudyStatus.OPENED,study.getStatus());
// TODO study의 openedDataTime이 null이 아닌지 확인
assertNotNull(study.getOpenedDateTime());
// TODO memberService의 notify(study)가 호출 됐는지 확인.
then(memberService).should().notify(study);
}