2024. 8. 23. 14:02ㆍ스프링
2024/08/23
오늘은 Mockito에 대해 알아보겠습니다.
다들 시원한 Mojito를 생각하셨나요?
이름은 비슷하지만 모히또가 아니라 모키토랍니다ㅎㅎ
※ Mockito란 무엇일까?
▶ ProductService의 단위 테스트 작성 시도
👉 다음 예시를 통해 mockito에 대해 알아봅시다.
● 관심 상품 최저가 업데이트
@Service
public class ProductService {
// ...
public static final int MIN_MY_PRICE = 100;
// ...
@Transactional
public Product updateProduct(Long id, ProductMypriceRequestDto requestDto) {
int myprice = requestDto.getMyprice();
if (myprice < MIN_MY_PRICE) {
throw new IllegalArgumentException("유효하지 않은 관심 가격입니다. 최소 " + MIN_MY_PRICE + " 원 이상으로 설정해 주세요.");
}
Product product = productRepository.findById(id).orElseThrow(() -> new NullPointerException("해당 상품을 찾을 수 없습니다."));
product.setMyprice(myprice);
return product.getId();
}
// ...
}
⭐⭐⭐⭐⭐
● 테스트 코드 작성
class ProductServiceTest {
@Test
@DisplayName("관심 상품 희망가 - 최저가 이상으로 변경")
void test1() {
// given
Long productId = 100L;
int myprice = ProductService.MIN_MY_PRICE + 3_000_000;
ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
requestMyPriceDto.setMyprice(myprice);
ProductService productService = new ProductService();
// when
ProductResponseDto result = productService.updateProduct(productId, requestMyPriceDto);
// then
assertEquals(myprice, result.getMyprice());
}
@Test
@DisplayName("관심 상품 희망가 - 최저가 미만으로 변경")
void test2() {
// given
Long productId = 200L;
int myprice = ProductService.MIN_MY_PRICE - 50;
ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
requestMyPriceDto.setMyprice(myprice);
ProductService productService = new ProductService();
// when
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
productService.updateProduct(productId, requestMyPriceDto);
});
// then
assertEquals(
"유효하지 않은 관심 가격입니다. 최소 " +ProductService.MIN_MY_PRICE + " 원 이상으로 설정해 주세요.",
exception.getMessage()
);
}
}
○ ProductService 의 updateProduct 메서드를 테스트하려면 ProductService 객체를 생성할 때
생성자로 ProductRepository, FolderRepository, ProductFolderRepository를 전달해야 한다.
○ 하지만 전달하려해도 인터페이스들인데 어떻게 전달하지❓
○ 그리고 또 전달한다고 해도 updateProduct 메서드 내부의 productRepository.findById(id) 코드는
어떻게 처리하지❓ 라는 의문이 든다.
▶ Mock object (가짜 객체)?
이상적으로, 각 테스트 케이스틑 서로 분리되어야 한다.
이를 위해 가짜 객체(Mock objec)를 생성하는 것도 좋은 방법이다.
by 단위테스트 (위키백과)
1. 분리 되기 어려운 클래스들
● Controller 클래스만 테스트할 수 없을까?
○ 테스트 범위: Controller, Service, Repository
● Service 클래스만 테스트할 수 없을까?
○ 테스트 범위: Service, Repository
● Repository 클래스만 테스트할 수 없을까?
○ 테스트 범위: Repository
2. 가짜 객체(Mock object)를 통한 분리 방법
● 가짜 객체(Mock object) 로 분리한다.
● MockRepository
○ 실제 객체와 겉만 같은 객체
▪ 동일한 클래스명, 함수명
○ 실제 DB 작업은 하지 않는다❗
▪ 마치 DB 작업이 이뤄지는 것처럼
▪ 테스트를 위해 필요한 결과값을 return
● 이하 간단히 'mock'(목)이라고 부른다.
▶ Mockito를 사용한 단위 테스트 구현
● Mockito framework: Mock 객체를 쉽게 만들 수 있는 방법 제공합니다.
● Mockito 적용하기
@ExtendWith(MockitoExtension.class) // @Mock 사용을 위해 설정합니다.
class ProductServiceTest {
@Mock
ProductRepository productRepository;
@Mock
FolderRepository folderRepository;
@Mock
ProductFolderRepository productFolderRepository;
@Test
@DisplayName("관심 상품 희망가 - 최저가 이상으로 변경")
void test1() {
// given
Long productId = 100L;
int myprice = ProductService.MIN_MY_PRICE + 3_000_000;
ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
requestMyPriceDto.setMyprice(myprice);
ProductService productService = new ProductService(productRepository, folderRepository, productFolderRepository);
// when
ProductResponseDto result = productService.updateProduct(productId, requestMyPriceDto);
// then
assertEquals(myprice, result.getMyprice());
}
@Test
@DisplayName("관심 상품 희망가 - 최저가 미만으로 변경")
void test2() {
// given
Long productId = 200L;
int myprice = ProductService.MIN_MY_PRICE - 50;
ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
requestMyPriceDto.setMyprice(myprice);
ProductService productService = new ProductService(productRepository, folderRepository, productFolderRepository);
// when
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
productService.updateProduct(productId, requestMyPriceDto);
});
// then
assertEquals(
"유효하지 않은 관심 가격입니다. 최소 " +ProductService.MIN_MY_PRICE + " 원 이상으로 설정해 주세요.",
exception.getMessage()
);
}
}
▶ Mockito를 사용한 단위 테스트 실행 결과
👉 에러가 발생한다.
WHY❓ 우리가 Mock 을 선언만 했지, 사용 케이스를 제대로 정의하지 못했기 때문이다.
▶ Mock 사용 케이스 추가
@ExtendWith(MockitoExtension.class) // @Mock 사용을 위해 설정합니다.
class ProductServiceTest {
@Mock
ProductRepository productRepository;
@Mock
FolderRepository folderRepository;
@Mock
ProductFolderRepository productFolderRepository;
@Test
@DisplayName("관심 상품 희망가 - 최저가 이상으로 변경")
void test1() {
// given
Long productId = 100L;
int myprice = ProductService.MIN_MY_PRICE + 3_000_000;
ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
requestMyPriceDto.setMyprice(myprice);
User user = new User();
ProductRequestDto requestProductDto = new ProductRequestDto(
"Apple <b>맥북</b> <b>프로</b> 16형 2021년 <b>M1</b> Max 10코어 실버 (MK1H3KH/A) ",
"https://shopping-phinf.pstatic.net/main_2941337/29413376619.20220705152340.jpg",
"https://search.shopping.naver.com/gate.nhn?id=29413376619",
3515000
);
Product product = new Product(requestProductDto, user);
ProductService productService = new ProductService(productRepository, folderRepository, productFolderRepository);
given(productRepository.findById(productId)).willReturn(Optional.of(product));
// when
ProductResponseDto result = productService.updateProduct(productId, requestMyPriceDto);
// then
assertEquals(myprice, result.getMyprice());
}
@Test
@DisplayName("관심 상품 희망가 - 최저가 미만으로 변경")
void test2() {
// given
Long productId = 200L;
int myprice = ProductService.MIN_MY_PRICE - 50;
ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
requestMyPriceDto.setMyprice(myprice);
ProductService productService = new ProductService(productRepository, folderRepository, productFolderRepository);
// when
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
productService.updateProduct(productId, requestMyPriceDto);
});
// then
assertEquals(
"유효하지 않은 관심 가격입니다. 최소 " +ProductService.MIN_MY_PRICE + " 원 이상으로 설정해 주세요.",
exception.getMessage()
);
}
}
● 사용 케이스를 추가했기 때문에 이제 오류 없이 테스트가 잘된다.
※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.
'스프링' 카테고리의 다른 글
스프링 - JPA 영속성 전이 (4) | 2024.08.28 |
---|---|
스프링 - 통합 테스트란? (0) | 2024.08.26 |
스프링 - 테스트 사용 방법 (0) | 2024.08.22 |
스프링 - JPA Query Methods란 무엇일까? (0) | 2024.08.21 |
스프링 - JPA Auditing 적용하기 (0) | 2024.08.20 |