Mock

이영진·2022년 2월 21일
0

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

Mockito: Mock 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법을 제공한다.

테스트를 작성하는 자바 개발자 50%+ 사용하는 Mock 프레임워크.
https://www.jetbrains.com/lp/devecosystem-2019/java/

현재 최신 버전 3.1.0

단위 테스트에 고찰
https://martinfowler.com/bliki/UnitTest.html

DB사용,외부API등을 호출한다고 가정할때, 그상황에서 외부 API를항상 사용하면서 작성할수없기때문에 Mock으로 만들어서 테스트

혹은 DB를사용할때 DAO,리포지토리를 Mock으로 만들고 그 객체가 어떻게 동작해야되는지를 mockito를 사용해서 코딩을하여 테스트하는것

단위테스트의 단위를 어떻게 정의를할것인가
MOck은 어느범위에서까지만 적용할것인가
등은 팀내에서 고민을하여 정하는것이 좋다.

외부서비는 mocking을 하는게 좋다고 생각한다.
외부서비스 조차도 테스트환경이 있다면 상관없지만 테스트환경이 없다면 mocking하는게 좋다.

studyservice, studyrepository 내가 작성한것들은 그상황에서는 굳이 내가 studycontroller를 unit test해야되는건가?
studyservice, studyrepository를 mock으로 만들어야하는가? 에대한것은 부정적이라고 한다.
하나의 컨트롤러호출을 행위로 보고 행위랑 연관되어있는 객체(repository, service) 는 그자체로 unit test라고 할수있지않을까라고한다.

모든객체별로 mock을 해야된다는 생각과 controller에 의존되어있는 연관객체들은 controller만을 행위1개로 보고 테스트해야된다는생각

Mockito 시작하기

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

스프링 부트 쓰지 않는다면, 의존성 직접 추가.

<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을 활용한 테스트를 쉽게 작성할 수 있다.

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

Mockito 레퍼런스
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

mock 객체만들기

public class StudyService {
    private final MemberService memberService;
    private final StudyRepository studyRepository;


    public StudyService(MemberService memberService, StudyRepository studyRepository) {
        this.memberService = memberService;
        this.studyRepository = studyRepository;
    }
}

다음과 같은코드의 테스트코드를 작성하려고할때 mock객체를 쓰기 아주좋은상황이다

class MapperTests {


    @Test
    void createStudyService() {
        MemberService memberService = new MemberService() {
            @Override
            public int hashCode() {
                return super.hashCode();
            }
        };

        StudyRepository studyRepository = new StudyRepository() {
            @Override
            public int hashCode() {
                return super.hashCode();
            }

            @Override
            public boolean equals(Object obj) {
                return super.equals(obj);
            }

            @Override
            protected Object clone() throws CloneNotSupportedException {
                return super.clone();
            }

            @Override
            public String toString() {
                return super.toString();
            }
        };
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);
    }

}

StudyService의 인스턴스를 테스트코드에서 만들어야하는데 MemberSerive, StudyRepository가 없기때문에 다음과같이 직접만들수도있지만 mockito가 이일을 만들어주지않고 할수있게해준다.

@ExtendWith(MockitoExtension.class) // @Mock애노테이션을 써서 mock을 사용하기위해 사용
class MapperTests {

    @Mock
    MemberService memberService;
    @Mock
    StudyRepository studyRepository;
    //방법 2

    @Test
    void createStudyService() {
        MemberService memberService = mock(MemberService.class);
        StudyRepository studyRepository = mock(StudyRepository.class);
        //방법 1 

        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);
    }

}



@ExtendWith(MockitoExtension.class) // @Mock애노테이션을 써서 mock을 사용하기위해 사용
class MapperTests {
    
    @Test
    void createStudyService(  @Mock MemberService memberService,@Mock
            StudyRepository studyRepository ) {
        // 메소드에만 mock을 만들고싶으면 다음과같이 파라미터에도 쓸수있다. (방법3)
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);
    }

}

Mock 객체 Stubbing

모든 Mock 객체의 행동

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

Mock 객체를 조작해서

  • 특정한 매개변수를 받은 경우 특정한 값을 리턴하거나 예외를 던지도록 만들 수 있다.
  • Void 메소드 특정 매개변수를 받거나 호출된 경우 예외를 발생 시킬 수 있다.
  • 메소드가 동일한 매개변수로 여러번 호출될 때 각기 다르게 행동호도록 조작할 수도 있다.
@ExtendWith(MockitoExtension.class) // @Mock애노테이션을 써서 mock을 사용하기위해 사용
class MapperTests {

    @Test
    void createStudyService(  @Mock MemberService memberService,@Mock
            StudyRepository studyRepository ) {

        Member member = new Member();
        when(memberService.findById(1L)).thenReturn(Optional.of(member)); // 1L라는 매개변수로 호출이되면 member가 리턴하는 stubbing 1L이아니면 리턴하지않는다
        when(memberService.findById(any())).thenReturn(Optional.of(member)); // 아무런값이든 리턴

        doThrow(new RuntimeException()).when(memberService).validate(1L); // void메소드를 stubbing하고싶을때 , 1L로 validate를 호출하면 예외를 던져라

        assertThrows(RuntimeException.class, () -> {
           memberService.validate(1L);
        });   // 진짜로 예외던지는지 확인 


        when(memberService.findById(1L))
                .thenReturn(Optional.of(member)) // 처음호출되면 이거실행
                .thenThrow(new RuntimeException()) // 두번호출되면 이거실행
                .thenReturn(Optional.empty()); // 세번째실행되면 이거실행

    }

}

0개의 댓글