본문 바로가기
Junit5

Junit5 에서 Mockito 사용하기

by doyoungKim 2021. 1. 2.

 

아래의 글을 작성하면서 Mockito 에 대해서 궁금해져서 공부를 해보았다.

 

Junit5 시작

​ What is JUnit 5? JUnit 5 Société Générale Use, Contribute and Attract: learn about Société Générale's open source strategy. junit.org junit-team/junit5 ✅ The 5th major version of the pr..

doyoung.tistory.com

 

사용한 코드는 아래의 깃허브 저장소에서 볼 수 있다.

 

doyoung0205/junit5-study

junit5-study repository. Contribute to doyoung0205/junit5-study development by creating an account on GitHub.

github.com

 

일단 Junit 과 마찬가지로 스프링부트 2.4 에서 spring-boot-starter-test 가 있으면 자동적으로 제공한다.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

 

Mock 이라는 것은

가짜 를 뜻한다.

가짜를 통해서 어떤 것을 테스트 하기 위해서 사용하는 것일까? 를 생각 해 보았을 때,
return 을 하는 어떤 메소드를 생각해보았다.

    public Person findById(Long id){
           
        ... 

        Person person = ... 

        return person;
    }

메소드가 return 을 하기 까지 어떠한 로직을 걸친다.

하지만 그 로직이 아직 완성이 되지 않았을 때, 전부 구현하기에는 무리일 때,
메소드가 return 하는 타입의 어떤 예측한 값이 나온다고 가정 해서 전체적인 흐름을 테스트 할 때, mock이 사용된다고 생각한다.

 

그렇다면 Mockito 는

Mock 을 다루는 프레임워크의 종류이다.
Spring boot 진영에서 가장 사용되는 프레임워크이다.

주로 Stub 이라는 기술을 사용한다.
Stub 이란 메소드의 결과를 미리 지정하는 것이다.

Person person = Person.builder().firstName("doyoung").lastName("kim").build();
when(personRepository.findById(anyLong())).thenReturn(person);

personRepository.findById 의 어떤 Long 값이 들어가도 결과는 미리 선언한 person 일 것이다.

 

실습 준비: Person 도메인 과 저장소

Person 이라는 도메인이 다음과 같이 만들어 보자.

// Person
package me.doyoung.junit5study.example.domain;

import lombok.*;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDate;

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    public enum Gender {
        F, M
    }

    private String firstName;
    private String lastName;
    private Gender gender;
    private LocalDate dateOfBirth;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Person(String firstName, String lastName, Gender gender, LocalDate dateOfBirth) {
        this(firstName, lastName);
        this.gender = gender;
        this.dateOfBirth = dateOfBirth;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public Gender getGender() {
        return gender;
    }

    public LocalDate getDateOfBirth() {
        return dateOfBirth;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
        result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Person other = (Person) obj;
        if (firstName == null) {
            if (other.firstName != null) {
                return false;
            }
        } else if (!firstName.equals(other.firstName)) {
            return false;
        }
        if (lastName == null) {
            if (other.lastName != null) {
                return false;
            }
        } else if (!lastName.equals(other.lastName)) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + firstName + ", lastName=" + lastName + "]";
    }
}

JPA 를 사용해서 PersonRepository 도 다음과 같이 만들어주자.

public interface PersonRepository extends JpaRepository<Person, Long> {

}

 

마지막으로 저장소에서 Person 을 찾는 서비스를 만들고 테스트로 넘어가자.

@Service
@RequiredArgsConstructor
public class PersonService {

    private final PersonRepository personRepository;

    public Person findById(Long id){
        return personRepository.findById(id).orElseThrow(IllegalAccessError::new);
    }


}

 

자, 이제 모든 준비는 끝났고 IntelliJ 라면 command + shift + T 를 통해 테스트 폴더로 넘어가자.

 

실습: Person 을 찾는 서비스를 테스트하자.

저장소에서 어떤 값이 나오는지 예측을 미리 하고 그 값이 나오면, PersonService 의 결과가 이것 일 것이다. 라는 형식의 테스트를 작성할 것 !

기본적으로  PersonService 를 테스트 하기 위해서는
필수적으로 personRepository 를 생성자의 주입시켜줘야하기 때문에
다음과 같이 셋팅을 해준다.

class PersonServiceTest {

    PersonRepository personRepository;

    PersonService personService;

    @BeforeEach
    void setup() {
        this.personService = new PersonService(personRepository);
    }
}

 

여기서 우리는 personRepository의 값을 예측할 것이기 때문에, Mock 의 대상자는 personRepository 가 된다.

따라서

    @Mock
    PersonRepository personRepository;

 

Mock 을 붙여 줍니다. 그리고 @Mock어노테이션을 사용하려면 Mockito 테스트 실행을 확장해주어야 하는데, 클래스 위의

@ExtendWith(MockitoExtension.class)

을 붙여준다.

최종코드

@ExtendWith(MockitoExtension.class)
class PersonServiceTest {

    @Mock
    PersonRepository personRepository;

    PersonService personService;

    @BeforeEach
    void setup() {
        this.personService = new PersonService(personRepository);
    }
}

 

이제 셋팅이 끝났으니, 다음과 같이 테스트 코드를 작성해 보자.

  • 성이 kim 인 person 을 미리 만들어 놓고, - given
  • 미리 만들어진 person 이 저장소에서 나왔을 때, - when
  • 저장소에서 나온 person 은 성이 kim 일 것이다. - then

    @Test
    @DisplayName("mock test")
    void mock_test() {

        Optional<Person> person = Optional.of(Person.builder().firstName("doyoung").lastName("kim").build());

        when(personRepository.findById(anyLong())).thenReturn(person);

        assertEquals(personService.findById(1L).getLastName(), "kim");

        // 목 객체의 findById 가 한번 실행되었는지 검증
        verify(personRepository, times(1)).findById(1L);

        // findAll 이 한번도 실행하지 않았는지 검증
        verify(personRepository, never()).findAll();

        // 해당 Mock 이 더 이상 interactional 발생되지 않아야 한다.
        verifyNoMoreInteractions(personRepository);

    }

실행 결과

 

메소드

설명

when

위에서 말했던 stub 을 하는 구문. 예측할 메소드를 지정.

anyLong():

어떠한 Long 값 이어도 라는 뜻을 가지고 있다.

thenReturn:

메소드의 결과값을 임의로 정한다.

verify

Mock 객체를 대상으로 검증한다.

 

728x90

'Junit5' 카테고리의 다른 글

Junit5 시작  (0) 2021.01.02

댓글