스프링 - Mockito란?

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()
        );
    }
}

                  ●  사용 케이스를 추가했기 때문에 이제 오류 없이 테스트가 잘된다.

 

 

 

 

 


※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.