Mockito не будет имитировать вызов метода с помощью `when ()`

#java #spring #unit-testing #mockito #springmockito

#java #весна #модульное тестирование #mockito #springmockito

Вопрос:

Я создаю REST Api с помощью SpringBoot. У меня есть три класса, ProductController , BackendService и SquareService . У всех этих классов есть вызываемый метод postProduct(ProductRequestBody request) , который возвращает разные типы в зависимости от того, о каком именно методе мы говорим. Итак, ProductController.postProduct(...) вызовы BackendService.postProduct() и BackendService.postProduct() вызовы SquareService.postProduct() , как показано на следующем графике потока:

Анатомия POST-запросов в моем API

Вот что Controller выглядит так:

 @RestController // So no serving views of any kind
@Component
@Slf4j
public class ProductController
{
    private final BackendService backendService;
    .
    .
    .
    @PostMapping(value = "/products")
    public ResponseEntity<ResponseMessage> postProduct(@RequestBody ProductPostRequestBody request)
    {
        if(!LiteProduct.PRODUCT_TYPES.contains(request.getProductType().toUpperCase()))
        {
            return failure("Invalid product type provided: Valid categories are: "  
                           new ArrayList<>(LiteProduct.PRODUCT_TYPES)   ".",
                           HttpStatus.BAD_REQUEST);
        }
        else if(request.getCostInCents() < 0)
        {
            return failure("Negative cost provided: "   request.getCostInCents()  ".",
                           HttpStatus.BAD_REQUEST);
        }
        else
        {
            try
            {

               /* ************************************************************** */
               /* ****** Here is the call to BackendService.postProduct() ****** */
               /* ************************************************************** */

                final BackendServiceResponseBody backendResponse = backendService.postProduct(request);

                final ProductResponseBody productResponse = ProductResponseBody.fromBackendResponseBody(backendResponse);
                return success("Successfully posted product!", productResponse);
            }
            catch (BackendServiceException exc)
            {
                return failure(exc, exc.getStatus());
            }
        }
    }
}
  

Вы можете увидеть вызов BackendService.postProduct() выше:
final BackendServiceResponseBody backendResponse = backendService.postProduct(request);

Я успешно использовал Mockito для моделирования этого вызова через класс с именем ControllerPostTests :

 @RunWith(SpringRunner.class)
@SpringBootTest
public class ControllerPostTests
{

    @Autowired
    private ProductController controller; // The class we are testing

    @MockBean
    private BackendService backendService;     // The class that will be mocked
    .
    .
    .

    @Test
    public void testOnePost()
    {
        final ProductPostRequestBody request = ProductPostRequestBody
                                                    .builder()
                                                        .name("Culeothesis Necrosis")
                                                        .productType("Flower")
                                                        .costInCents(600L) // 'L for long literal
                                                    .build();

        final BackendServiceResponseBody expected = BackendServiceResponseBody.builder()
                                                                            .name(request.getName())
                                                                            .itemId("RANDOM_ITEM_ID")
                                                                            .itemVariationId("RANDOM_ITEM_VAR_ID")
                                                                            .productType(request.getProductType())
                                                                            .costInCents(request.getCostInCents())
                                                                            .isDeleted(false)
                                                                        .build();

       /* ***************************************************************************** */
       /* ************ Here's the call that gets successfully mocked: ***************** */
       /* ***************************************************************************** */

        when(backendService.postProduct(request)).thenReturn(expected);

        final ResponseEntity<ResponseMessage> responseEntity = controller.postProduct(request);
        final ProductResponseBody response = checkAndGet(responseEntity);
        assertTrue("Request did not match response", responseMatchesPostRequest(request, response));
    }
  

And this test, alongside a more contrived one, works perfectly, and also passes (yay!).

Now, what about BackendService ? I want to mock the SquareService to debug that too, and I will also have to mock a class called LiteRepository which is effectively a JPARepository :

 @Slf4j
@Component
public class BackendService
{
    private final SquareService squareService; // Another class that is called by the methods of `this`.
    private final LiteProductRepository localRepo;  // A JPARepository I'm using as a cache.
    .
    .
    .
    public BackendServiceResponseBody postProduct(ProductPostRequestBody request) throws BackendServiceException
    {
        // First, make a local check to ensure that there's no name clash for
        // the product uploaded. This is one of the advantages of having a cache.
        if(localRepo.findByName(request.getName()).isPresent())
        {
            final ResourceAlreadyCreatedException exc = new ResourceAlreadyCreatedException();
            logException(exc, this.getClass().getEnclosingMethod().getName());
            throw new BackendServiceException(exc, HttpStatus.CONFLICT);
        }
        else
            // We first POST to Square and *then* store the cached version in our
            // local DB in order to grab the unique ID that Square provides us with.
        {
            try
            {
               /* ************************************************************************* */
               /* ********** Here's the call that I need mocked: *************************** */
               /* ************************************************************************* */

                final SquareServiceResponseBody response = squareService.postProduct(request);

               /* ************************************************************************* */
               /* **** This call also needs to be mocked, but let's deal with it later.**** */
               /* ************************************************************************* */

                localRepo.save(LiteProduct.fromSquareResponse(response));
                return BackendServiceResponseBody.fromSquareResponseBody(response);
            }
            catch(SquareServiceException exc)
            {
                logException(exc, this.getClass().getEnclosingMethod().getName());
                throw new BackendServiceException(exc, exc.getStatus());
            }
        }
    }
  

Что меня сбивает с толку, так это то, что в отдельном тестовом файле я следовал точно такому же подходу, которому я следовал в моем ProductControllerTests , но, к сожалению, вызов не издевается, и код фактически выполняет метод SquareService.postProduct(...) , который мне нужно издеваться:

 @RunWith(SpringRunner.class)
@SpringBootTest
public class BackendPostTests
{

    /* *********************************************************************************************************** */
    /* ************************************ Fields and utilities ************************************************** */
    /* *********************************************************************************************************** */

    @Autowired
    private BackendService backendService; // The class we are testing

    @MockBean
    private SquareService squareService;     // One class that will be mocked

    @MockBean
    private LiteProductRepository repository;     // Another class that will be mocked

     .
     .
     .

    @Test
    public void testOnePost()
    {
        final ProductPostRequestBody request = ProductPostRequestBody
                                                    .builder()
                                                        .name("Pink handbag")
                                                        .productType("Accessory")
                                                        .costInCents(600L) // 'L for long literal
                                                    .build();

        final SquareServiceResponseBody expected = SquareServiceResponseBody.builder()
                                                                          .name(request.getName())
                                                                          .itemId("RANDOM_ITEM_ID")
                                                                          .itemVariationId("RANDOM_ITEM_VAR_ID")
                                                                          .productType(request.getProductType())
                                                                          .costInCents(request.getCostInCents())
                                                                          .isDeleted(false)
                                                                     .build();
                 
       /* *********************************************************************** */
       /* ******* This is where I mock SquareService.postProduct() ************** */
       /* *********************************************************************** */
        when(squareService.postProduct(request)).thenReturn(expected);

        final LiteProduct cachedMiniProduct = LiteProduct.fromSquareResponse(expected);

       /* *********************************************************************** */
       /* * And this is where I hope to mock LiteProductRepository.save() later * */
       /* *********************************************************************** */
        when(repository.save(cachedMiniProduct)).thenReturn(cachedMiniProduct);
        
        final BackendServiceResponseBody response = backendService.postProduct(request);
        assertTrue("Request did not match response", responseMatchesPostRequest(request, response));
    }
  

! Я смог определить, что код определенно входит в этот метод через отладчик IntelliJ. Строка when(squareService.postProduct(request)).thenReturn(expected); , похоже, не работает.

Что я здесь делаю не так?

// Редактировать: улучшенное изображение.

Комментарии:

1. Idk как when(squareService.postProduct(request)).thenReturn(expected); не выдал «Недопустимое использование сопоставителей аргументов».

Ответ №1:

Вы должны попробовать Mockito.any(ProductPostRequestBody.class) , чтобы это выглядело так.

 when(squareService.postProduct(Mockito.any(ProductPostRequestBody.class))).thenReturn(expected);
  

Комментарии:

1. К сожалению, это тоже не сработало; У меня точно такая же проблема с вводом метода. 🙁

2. Может выдать мне ошибку !. определенно, я помогу вам с этим