아래의 글을 작성하면서 Mockito 에 대해서 궁금해져서 공부를 해보았다.
사용한 코드는 아래의 깃허브 저장소에서 볼 수 있다.
일단 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 객체를 대상으로 검증한다. |
댓글