테스트 더블이란 테스트 코드를 작성할 때, 실제 객체를 대신하여 사용하는 대체 객체를 의미한다.
이번에 페어 로건 덕분에 테스트 더블의 한 종류인 Mock을 처음 접했는데, 이걸 사용해 테스트를 했던 방법을 한번 기록해보았다.
(Mock이 뭔지 설명하고 싶지만 아직 Stub와 Mock의 차이를 잘 이해하지 못하였으므로 일단 생략한다.)
Mock을 사용하기 위해 Mockito라는 프레임워크를 이용하였다.
Mockito는 자바에서 테스트 더블을 쉽게 사용할 수 있도록 도와주는 프레임워크이다.
테스트하고 싶은 클래스
아래와 같이 Dao에 의존하는 서비스 클래스를 테스트하고 싶었다. 하지만 데이터베이스와 관련한 테스트는 이미 Dao 테스트에서 하였다. 나는 서비스에서는 Dao의 특정 메서드를 호출하면, 그 메서드의 반환한 값에 따라 서비스에서 올바른 동작을 해주는지만을 검증하고 싶었다. 아래의 코드에서는 productDao.update 메서드가 1을 반환하면 예외가 발생하지 않고, 0을 반환하면 예외가 발생하는 것을 검증하고 싶었다. 그래서 Mock을 이용하고자 했다.
@Service
public class ProductManagementService {
private final ProductDao productDao;
public ProductManagementService(final ProductDao productDao) {
this.productDao = productDao;
}
public void update(final ProductDto productDto) {
int updatedRowCount = productDao.update(ProductEntityMapper.from(productDto));
if(updatedRowCount == 0){
throw new IllegalArgumentException("존재하지 않는 상품입니다.");
}
}
}
의존성 추가
mockito를 따로 의존성을 추가해주어야 한다.
dependencies {
testImplementation 'org.mockito:mockito-core:4.5.1'
}
그런데 나는 스프링 부트를 사용하기 때문에 다음 의존성만으로 퉁칠 수 있다.
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
@ExtendWith(MockitoExtension.class) 어노테이션 추가
Mock을 이용해 테스트할 클래스 위에 어노테이션을 달아주어야 한다.
이 어노테이션을 달면 아래에 나오는 @InjectMocks, @Mock을 사용할 수 있다.
@ExtendWith(MockitoExtension.class)
class ProductManagementServiceTest {
}
@InjectMocks
Mock을 주입할 객체를 지정하기 위한 어노테이션이다. Mock 객체를 주입하고 싶은 대상 위에 적어준다.
@InjectMocks
ProductManagementService managementService;
@Mock
Mock으로 설정할 객체 위에 적어준다. 나는 서비스에서 사용하는 Dao를 Mock으로 만들어주었다.
@Mock
JdbcProductDao productDao;
이제 테스트를 써보자!
productDao.update 메서드가 0을 반환하도록 설정해주고 테스트를 하는 코드이다.
@DisplayName("상품 데이터가 수정되지 않으면 예외가 발생하는지 확인한다")
@Test
void failTest() {
final ProductDto productDto = ProductDto.of(3L, "pobi_doll", "https://cdn.polinews.co.kr/news/photo/201910/427334_3.jpg", 10000000);
when(productDao.update(any())).thenReturn(0); // Mock 객체의 특정 메서드를 호출했을 때 반환값을 0으로 지정
assertThatThrownBy(() -> managementService.update(productDto))
.isInstanceOf(IllegalArgumentException.class);
}
만약 메서드의 반환값이 void라면 다음과 같이 코드를 써볼 수 있다.
@DisplayName("상품 데이터가 등록되는지 확인한다")
@Test
void saveTest() {
final ProductDto productDto = ProductDto.of("pobi_doll", "image", 10000000);
final ProductDto productDto = ProductDto.of("pobi_doll", "https://cdn.polinews.co.kr/news/photo/201910/427334_3.jpg", 10000000);
doNothing().when(productDao).insert(any()); // Mock 객체의 특정 메서드를 호출했을 때 반환값이 void일 때 사용
managementService.save(productDto);
assertAll(
() -> verify(productDao, times(1)).insert(any()) // Mock 객체의 특정 메서드가 1번 호출되었는지 검증
);
}
최종 테스트 코드 형태
@ExtendWith(MockitoExtension.class)
class ProductManagementServiceTest {
@InjectMocks
ProductManagementService managementService;
@Mock
JdbcProductDao productDao;
@Nested
@DisplayName("상품을 수정하는 update 메서드 테스트")
class UpdateTest {
@DisplayName("상품 데이터가 수정되는지 확인한다")
@Test
void successTest() {
final ProductDto productDto = ProductDto.of(1L, "pobi_doll", "https://cdn.polinews.co.kr/news/photo/201910/427334_3.jpg", 10000000);
when(productDao.update(any())).thenReturn(1);
assertDoesNotThrow(() -> managementService.update(productDto));
}
@DisplayName("상품 데이터가 수정되지 않으면 예외가 발생하는지 확인한다")
@Test
void failTest() {
final ProductDto productDto = ProductDto.of(3L, "pobi_doll", "https://cdn.polinews.co.kr/news/photo/201910/427334_3.jpg", 10000000);
when(productDao.update(any())).thenReturn(0);
assertThatThrownBy(() -> managementService.update(productDto))
.isInstanceOf(IllegalArgumentException.class);
}
}
}
'프로그래밍 > JAVA Spring' 카테고리의 다른 글
[Spring 스프링] HandlerInterceptorAdapter 말고 HandlerInterceptor (0) | 2023.05.07 |
---|---|
[Spring 스프링] @SpringBootTest로 테스트할 때 데이터베이스 롤백하기 (4) | 2023.05.07 |
[Spring 스프링] Spring Core - Bean과 Configuration (2) | 2023.04.24 |
[Spring 스프링] Spring Core - IoC(Inversion of Control), DI(Dependency Injection) (0) | 2023.04.24 |
[Spring 스프링] Layered Architecture (0) | 2023.04.23 |