Jason
Jason

Reputation: 2915

Mockito won't mock method invokation with `when()`

I'm building a REST Api using SpringBoot. I have three classes, ProductController, BackendService and SquareService. All of these classes have a method called postProduct(ProductRequestBody request), which return different types based on which exact method we are talking about. So, ProductController.postProduct(...) calls BackendService.postProduct(), and BackendService.postProduct() calls SquareService.postProduct(), as the following flow graphic shows:

Anatomy of a POSt requestsin my API

Here is what Controller looks like:

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

You can see the call to BackendService.postProduct() above: final BackendServiceResponseBody backendResponse = backendService.postProduct(request);

I have used Mockito successfully to mock that call through a class called 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());
            }
        }
    }

What baffles me is that, in a separate test file, I have followed exactly the same approach that I have followed in my ProductControllerTests, but unfortunately the call is not mocked, and the code actually executes the method SquareService.postProduct(...), which I need mocked:

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

! I have been able to determine that the code definitely goes inside that method through the IntelliJ debugger. The line when(squareService.postProduct(request)).thenReturn(expected);does not seem to work.

What am I doing wrong here?

// Edit: Improved image.

Upvotes: 2

Views: 316

Answers (1)

Anirudh Jadhav
Anirudh Jadhav

Reputation: 1007

You should try Mockito.any(ProductPostRequestBody.class) so this would look like.

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

Upvotes: 1

Related Questions