서비스 계층 테스트
- 서비스 계층 테스트가 유용한 이유.
- 서비스 레이어에서는 직접적으로 Spring 에 접근하여 테스트할 필요 없이
코드의 비지니스 로직만을 테스트 할 수 있게 해야 하기 때문에@SpringBootTest
나@DataJpaTest
와 같은 테스트 실행시 build 에 시간이 오래 소요되는 작업을 스킵할 수 있음. - 수행되는 속도가 빠름으로 내가 만든 로직을 만들어가면서 계속 테스트할 수 있음.
- 유지보수성, 테스트 용이성, 확장성을 증가 시킬 수 있음.
- 서비스 레이어에서는 직접적으로 Spring 에 접근하여 테스트할 필요 없이
@ExtendWith(MockitoExtension.class)
Mockito
를 테스트 클래스에서 사용하게 해줌.Mock
,Stub
객체를 생성할 수 있게 해줌
@Mock
- 가짜 구현체를 선언하게 해줌
- 가짜 구현체를 통해 특정 행위를 Stubbing 하고 값을 return 받는것처럼 꾸며줌
- 테스트의 대상이 되는 객체의 의존성 주입을 위한 선언
- 예를 들어
AService
가 의존하고 있는ARepository
가 있다고 할때ARepository
를@Mock
한다.
- 예를 들어
@InjectMocks
- 가짜 구현체를 선언하게 해줌
- 테스트의 대상이 되는 객체 선언
- 예를 들어
AService
가 의존하고 있는ARepository
가 있다고 할때AService
를@InjectMocks
한다.
- 예를 들어
@BeforeEach
- 각각의 테스트가 실행되기 전 항상 실행되게 하는 어노테이션, 테스트 오브젝트간 독립성 유지를 위해 사용한다.
@AfterEach
도 있다.
when().thenReturn();
when(customerRepository.save(any(Customer.class))).thenReturn(customerDto.toEntity())
- 위의 코드는
when(...)
이 일어나면thenReturn(...)
를 반환한다는 선언 이걸 stubbing 라고 한다.
- 위의 코드는
테스트 예시가될 Service 코드와 메소드
- 진짜로 서비스 코드의 로직을 실행시키는지 테스트를 하기 위해 로직이 실행된 후 phone 값을 임의로 변경.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@Service
@RequiredArgsConstructor
public class CustomerServiceImpl implements CustomerService {
private final CustomerRepository customerRepository;
@Override
public CustomerDto save(CustomerDto customerDto) {
Customer save = customerRepository.save(customerDto.toEntity());
log.info("save entity = {}", save);
save.setPhone("mock test!!!");
return save.toDto();
}
}
테스트 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.spring.example.jpa.dto.CustomerDto;
import org.spring.example.jpa.entity.Customer;
import org.spring.example.jpa.service.CustomerServiceImpl;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@Slf4j
@ExtendWith(MockitoExtension.class)
public class RepositoryTest {
@Mock
private CustomerRepository customerRepository;
@InjectMocks
private CustomerServiceImpl customerService;
private CustomerDto customerDto;
@BeforeEach
void setUp() {
customerDto =
CustomerDto.builder()
.customerId("johnDoe@gmail.com")
.firstName("john")
.lastName("doe")
.address("동작대로 xx길 xxx xx")
.phone("555-0101")
.build();
}
@Test
void customer_save_ok_1() {
when(customerRepository.save(any(Customer.class))).thenReturn(customerDto.toEntity());
CustomerDto save = customerService.save(new CustomerDto());
Assertions.assertNotNull(save);
Assertions.assertNotEquals(customerDto.getPhone(), save.getPhone());
Assertions.assertEquals(save.getPhone(), "mock test!!!");
}
}
- 첫번째 필드 : 실제로 테스트의 대상이 되는 Service에 필요한 의존성을
@Mock
한다.
1
2
3
@Mock
private CustomerRepository customerRepository;
- 두번째 필드 : 실제로 테스트의 대상이 되는 Service를 설정한다.
1
2
3
@InjectMocks
private CustomerServiceImpl customerService;
- 세번째 필드 : subbing 될 객체를 전역 변수로 빼놓는다.(테스트 하기 쉽게 하기 위함.)
1
private CustomerDto customerDto;
void setUp()
: 테스트 실행전 초기화 작업을 위해 설정, 해당 값을 stubbing에 사용할 예정.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@BeforeEach
void setUp() {
customerDto =
CustomerDto.builder()
.customerId("johnDoe@gmail.com")
.firstName("john")
.lastName("doe")
.address("동작대로 xx길 xxx xx")
.phone("555-0101")
.build();
// public Customer toEntity() {
// return Customer.builder()
// .customerId(customerId)
// .firstName(firstName)
// .lastName(lastName)
// .address(address)
// .phone(phone)
// .build();
// }
}
void customer_save_ok_1()
: 실제 테스트가 될 코드when(customerRepository.save(any(Customer.class))).thenReturn(customerDto.toEntity())
- when 인자 값으로
customerRepository.save(any(Customer.class))
가 실행되면
thenReturn 인자 값을 선언된 객체를 반환한다. 해당 테스트에서는Customer
반환.any(Class<T> type)
: 인자값으로 받을 클래스 타입 설정. 타입만 맞으면 thenReturn 을 보장하겠다는 표현.
- when 인자 값으로
CustomerDto save = customerService.save(new CustomerDto());
- when에 설정한 대로 클래스 타입만 맞춘 후 thenReturn 되는 stubbing 객체를 반환받음.
- 기타 검증을 위한 코드
Assertions.assertNotNull(save);
- 객체가 잘 리턴 되었는지 확인
Assertions.assertNotEquals(customerDto.getPhone(), save.getPhone());
- 실제 서비스로직을 작동 시켰는지 확인, save 메소드 안에서 phone 값을 바꾸엇음으로 해당 검증은 통과되어야한다.
Assertions.assertEquals(save.getPhone(),"mock test!!!");
- 값이 바뀐것만으로는 못 믿겠으니까 실제 값을 비교
- 테스트 결과
- Spring boot 안에 설정된 서비스를 실행시키는 테스트지만 실행 시간이 2.3초정도로 매우 짧다.
- 콘솔을 확인해보면 Spring Bean 들을 전혀 안긁어옴.
- 테스트 통과.
- Spring boot 안에 설정된 서비스를 실행시키는 테스트지만 실행 시간이 2.3초정도로 매우 짧다.