2

我正在使用 SpringBoot 构建一个 REST Api。我有三个班级ProductControllerBackendServiceSquareService。所有这些类都有一个名为 的方法postProduct(ProductRequestBody request),它根据我们正在谈论的确切方法返回不同的类型。因此,ProductController.postProduct(...)调用BackendService.postProduct()BackendService.postProduct()调用SquareService.postProduct(),如下面的流程图所示:

我的 API 中的 POST 请求剖析

这是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));
    }

这个测试,连同一个更人为的测试,完美地工作,并且也通过了(耶!)。

现在,怎么样BackendService?我也想模拟它SquareService来调试它,而且我还必须模拟一个名为的类LiteRepository,它实际上是一个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());
            }
        }
    }

让我感到困惑的是,在一个单独的测试文件中,我遵循了与在.ProductControllerTestsSquareService.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);似乎不起作用

我在这里做错了什么?

// 编辑:改进的图像。

4

1 回答 1

1

你应该试试Mockito.any(ProductPostRequestBody.class)这样看起来像。

when(squareService.postProduct(Mockito.any(ProductPostRequestBody.class))).thenReturn(expected);
于 2020-10-23T14:51:01.760 回答