본문 바로가기
TIL : Today I learned (or Week)

TIL 230714 : MOCK객체란 (사용케이스 추가, given, willReturn)

by 우인입니다 2023. 7. 18.

지난 시간 테스트 코드에 대해서 조금 알아보았다.

진행을 하다가 이런 의문이 들었다.

 

클래스간의 의존성은 어떻게 해결하지?

예를 들어 Controller를 테스트하려면 주입받아온 Service, 또 거기서 주입받아 온 Repository는 또 설계해 줘야하나?

 

이럴 때, 필요한 객체가 Mock객체, 가짜 객체이다.

 

흔히 목업자료할 때 쓰이는 그 단어.

 


Mockito

귀여운 네이밍 센스의 뫀히토 라는 Mockito framework: Mock객체를 통해 이를 쉽게 해준다고 합니다.

그냥 된다라기보단 어떤 것으로 부터 기능을 제공받고 있는 지를 아는 것이 조금은 중요한 듯 해서 참고차 기록해둡니다.

 

사용문법

@ExtendWith(MockitoExtension.class) // @Mock 사용을 위해 설정합니다.
class ~~~Test {
...

맨 처음으로 mock을 사용하겠다라는 것을 명시합니다.

 

@Mock
ProductRepository productRepository;

@Mock
FolderRepository folderRepository;

그리고 빈을 주입받아와야할 곳에 @Mock을 넣어줍니다.

 

 

 

그렇게 되면 위처럼 실제로 주입받았었던 Repository를 가져오는 것이 아닌 가짜 Mock객체를 넣어줍니다.

실제 DB가 실행이 되는 것은 아니지만 Service객체가 잘 작동하는 지 테스트를 위한 Mock객체인 셈.

 

그러면 같은 클래스명과 메소드명으로 테스트를 진행할 수는 있지만 실제 데이터가 없는데 어떻게 테스트가 진행될 수가 있을까?

 

 

// when
        ProductResponseDto result = productService.updateProduct(productId, requestMyPriceDto);

우선 테스트 실행부에 있는 코드. productService mock객체가 가지고 있는 updateProduct메소드를 테스트 해야한다.

 

updateProduct 메소드를 살펴보자.

public ProductResponseDto 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.update(requestDto);

    return new ProductResponseDto(product);
}

 

코드 중에서 아래 부분에서 문제가 있다.

 Product product = productRepository.findById(id).orElseThrow(() ...

 

productRepository에서 product를 찾아와야 하는데 mock객체라서 product를 가져올 수 없다.

이 부분은 우리가 테스트해야할 부분이 아니기 때문에 그냥 product를 하나 만들어서 넣어 줌으로써

우리가 진짜로 테스트해야하는 update 기능이 잘 되는지 확인해야한다.

그리고 이렇게 product를 넣어주는 걸 사용케이스 추가라고 한다.

 

사용케이스 추가

product를 productRepository에서 찾아올 수 없어서 오류가 발생한다.

Mock객체를 통해서 메소드가 온전히 실행만 되게 했지, 실제로 모든 데이터를 mock객체의 역할을 하던 실제객체에서 가져올 수 없기 때문이다.

 

 

User user = new User();
        
ProductRequestDto requestProductDto = new ProductRequestDto(
        "닌텐도 스위치",
        "https://~~.jpg",
        "https://search.~~",
        351500
);

Product product = new Product(requestProductDto, user);

위처럼 직접 하드코딩해서 product객체 하나를 만들어준다.

 

그러면 productRepository에서 원래 찾아왔어야 했는데, mock객체인 productRepository에서 가져온 것처럼 해주는 원리.?

 

그리고 중요한 것은 우리가 준비한 사용케이스를 사용하게끔 설정해두는 코드를 아래와 같이 넣어준다.

given(productRepository.findById(productId)).willReturn(Optional.of(product));

productRepository.findById(productId)에 대한 리턴을 우리가 만들어둔 Optional.of(product)으로 해주겠다는 의미이다.

 

이렇게 되면 updateProduct메소드가 실행되는 도중에 productRepository.findById(productId)가 mockRepository이므로 제대로 된 데이터를 찾아와 줄 수 없는것이 아니라 우리가 준비해둔 product를 리턴해줄 수가 있다.

 


뭔 말인지 알겠는데, 어떻게 하라는 건지 모르겠다.
(공부하겠다라는 뜻)