Mock

Jiwon Park·2024년 11월 10일

Mock: 진짜 객체와 비슷하게 동작하지만 프로그래머가 직접 그 객체의 행동을 관리하는 객체.

Mockito: Mock 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법을 제공하는 프레임워크

스프링부트 프로젝트 생성시 spring-boot-starter-test에서 자동으로 Mockito를 추가해 준다.

테스트할 때 db혹은 외부 api등을 항상 사용하면서 테스트할 수 없다.
그런 경우 mock으로 만들어 어떻게 동작할지, 어떤 응답이 올지 가정해서 테스트를 할 수 있다.
mock을 사용한다면 repository가 구현이 안되어있어도, 외부 서비스 없이도 테스트를 만들수 있다.


Mockito 시작하기

  • Mock을 만드는 방법
  • Mock이 어떻게 동작해야 하는지 관리하는 방법
  • Mock의 행동을 검증하는 방법

Mockito 레퍼런스

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html


Mock 객체 만들기

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);

    }
}
  1. Mockito.mock() 메소드로 만드는 방법
    MemberService memberService = mock(MemberService.class);
    StudyRepository studyRepository = mock(StudyRepository.class);
  1. @Mock 애노테이션으로 만드는 방법
  • JUnit 5 extension으로 MockitoExtension을 사용해야 한다.
@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 객체 Stubbing

기본 Mock 객체의 행동
Null을 리턴한다. (Optional 타입은 Optional.empty 리턴)
Primitive 타입은 기본 Primitive 값.
콜렉션은 비어있는 콜렉션.
Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않는다.

Stubbing이란 Mock 객체를 조작해서

  • 특정한 값을 리턴하거나 예외를 던지도록 만들 수 있다.

return 있는 메소드 stubbing 하기

when([메소드]).thenReturn([리턴값])
when([메소드]).thenThrow([익셉션])

return 없는 메소드 예외 발생 stubbing 하기

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 -> 메서드가 실제로 호출되었는지 검증        
    }

Mockito BDD 스타일 API

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);
profile
안녕하세요

0개의 댓글