Mock = 진짜 객체와 비슷하게 동작하지만 프로그래머가 직접 그 객체의 행동을 관리하는 객체
Mockito = Mock 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법을 제공, 많이 쓰임
Mock 생성
public StudyService(MemberService memberService, StudyRepository repository) {
assert memberService != null;
assert repository != null;
this.memberService = memberService;
this.repository = repository;
}
test에서 StudyService를 만들어서 써야하지만 MemberService, StudyRepository는 interface만 존재하는 상황이다. 이런 상황에서 mocking을 사용할 수 있다.
StudyRepository studyRepository = mock(StudyRepository.class);
Mockito.mock 메소드로 직접 만드는 방법이 존재하고
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
@Test
void createStudyService(@Mock MemberService memberService,
@Mock StudyRepository studyRepository) {
StudyService studyService = new StudyService(memberService, studyRepository);
assertNotNull(studyService);
}
}
확장팩 MockitoExtension 을 써야 @Mock 했을때 null이아니라 제대로 mock 객체를 만들어준다.
필드에 선언하는 경우는 여러 테스트에서 쓰는 경우에 쓰인다. 위 예시는 해당 메소드에서만 쓰이기 때문에 인자로 줌
Mock 객체 Stubbing = Mock 객체의 행동을 조작하는것
Mock객체는 default로
1. Null 을 리턴한다. (Optional 타입의 경우 Optional.empty() 로 나온다)
2. Primitive타입은 Privmitive 타입을 리턴
3.콜렉션은 비어있는 콜렉션
4.Void 메소드는 에외를 던지지 않고 아무런 일도 발생하지 않음 -> 그냥 지나감
@Test
void createNewStudy(@Mock MemberService memberService,
@Mock StudyRepository studyRepository) {
StudyService studyService = new StudyService(memberService, studyRepository);
assertNotNull(studyService);
Member member = new Member();
member.setId(1L);
member.setEmail("keesun@email.com");
when(memberService.findById(1L))
.thenReturn(Optional.of(member));
Study study = new Study(10, "java");
Optional<Member> findById = memberService.findById(1L);
assertEquals("keesun@email.com",findById.get().getEmail());
mock 객체인 memberservice에서 해야할 행동을 when 에서 정의할 수 있다. 이때 인자로 1L이 들어오는 것만 정의 하였기 때문에 아래에서 사용할떄 2L 같은 다른 인자를 주게되면 기본 값인 Optional.empty() 가 튀어나올 것이다.
인자로 들어오는것과 매칭되는것만 정의되는 개념이 Arguement matcher이다.
when(memberService.findById(any()))
.thenReturn(Optional.of(member))
.thenThrow(new RuntimeException())
.thenReturn(Optional.empty());
stubbing consecutive calls 의 예시이다. 3번 호출 되면 차례대로 명시한것들이 튀어나오게 된다.
mock 객체 확인
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 + "'")));
Study newstudy = repository.save(study);
memberService.notify(newstudy);
memberService.notify(member.get());
return newstudy;
}
studyService.createNewStudy(1L, study);
assertEquals(member, study.getOwner());
verify(memberService, times(1)).notify(study);
verify(memberService,never()).validate(any());
verifyNoMoreInteractions(memberService);
InOrder inOrder = inOrder(memberService);
inOrder.verify(memberService).notify(member);
inOrder.verify(memberService).notify(study);
테스트코드에서 mock 객체인 studyService#createNewStudy 메소드 안에있는 notify 가 1번 호출이 되었는지 확인 할 수 있다.
createNewStudy 안의 notify를 주석처리 하면 테스트가 실패하게 된다.
verifyNoMoreInteraction의 경우 아래의 inorder.notify 때문에 테스트가 실패하게 된다.
메소드의 호출 순서를 verify 할 수있다. inOrder 메소드를 통해서 차례대로 호출이 되고 있는지 확인하게 된다.
BDD = Behavitor Driven Development
행동에 대한 스펙을 정의한다.
@Test
void createNewStudy() {
// Given
StudyService studyService = new StudyService(memberService, studyRepository);
assertNotNull(studyService);
Member member = new Member();
member.setId(1L);
member.setEmail("keesun@email.com");
Study study = new Study(10, "테스트");
given(memberService.findById(1L)).willReturn(Optional.of(member));
given(studyRepository.save(study)).willReturn(study);
// When
studyService.createNewStudy(1L, study);
// Then
assertEquals(member, study.getOwner());
then(memberService).should(times(1)).notify(study);
then(memberService).shouldHaveNoMoreInteractions();
}
given -> when -> then 의 논리 template을 적용한다.
'WEB > Java Test' 카테고리의 다른 글
운영 이슈 + 아키텍처 테스트 (0) | 2022.11.14 |
---|---|
TestContainers (1) | 2022.11.11 |
JUnit5 (0) | 2022.11.10 |