获取 org.mockito.exceptions.misusing.PotentialStubbingProblem:严格存根参数不匹配

Mar*_*tin 3 java junit unit-testing mockito

我在我的应用程序中使用公共 API Nutritionix 从他们的数据库中获取有关食品成分的详细信息。我想测试一种方法,该方法向公共 API 发送 POST 请求以接收有关产品营养价值的详细信息,然后在我的应用程序的下一个过程中使用它们。

我的测试类如下所示:

@ExtendWith(MockitoExtension.class)
class CaloriesServiceTest {

  @InjectMocks
  private CaloriesService caloriesService;

  @Mock
  private RestTemplate restTemplate;

  @Mock
  private NutritionixHeader nutritionixHeader;

  @BeforeEach
  void setUp() {
    caloriesService = new CaloriesService(nutritionixHeader, restTemplate);
  }

  @Test
  void receiveNutritionInformationFromAPI() {

    given(nutritionixHeader.getNutritionixAppId()).willReturn("secretID");
    given(nutritionixHeader.getNutritionixAppKey()).willReturn("secretKey");

    var meal1 = Meal.builder()
        .mealIngredients(List.of(RecipeIngredient.builder()
            .foodName("grilled fish")
            .build()))
        .build();
    var meal2 = Meal.builder()
        .mealIngredients(List.of(RecipeIngredient.builder()
            .foodName("eggs")
            .build()))
        .build();
    var meal3 = Meal.builder()
        .mealIngredients(List.of(RecipeIngredient.builder()
            .foodName("bacon")
            .build()))
        .build();

    var dailyMeals = DailyMeals.builder().dailyMeals(List.of(meal1, meal2, meal3)).build();

    var foodNutritional1 = FoodNutritional.builder().foodName("food name1").calories(11.1).build();
    var foodNutritional2 = FoodNutritional.builder().foodName("food name2").calories(22.2).build();
    var foodNutritional3 = FoodNutritional.builder().foodName("food name3").calories(33.3).build();
    var foodNutritional4 = FoodNutritional.builder().foodName("food name4").calories(44.4).build();

    HttpEntity<NutrientsBodyForRequest> requestBody =
        new HttpEntity<>(NutrientsBodyForRequest.builder()
            .query("grilled fish | eggs | bacon | burger")
            .build());

    given(restTemplate
        .exchange("https://trackapi.nutritionix.com/v2/natural/nutrients",
            HttpMethod.POST,
            requestBody,
            new ParameterizedTypeReference<List<FoodNutritional>>() {
            }))
        .willReturn(
            new ResponseEntity<List<FoodNutritional>>(
                List.of(
                    foodNutritional1,
                    foodNutritional2,
                    foodNutritional3,
                    foodNutritional4),
                HttpStatus.OK));

    //when
    List<FoodNutritional> foodsNutritional =
        caloriesService.receiveNutritionInformationFromAPI(dailyMeals);

    assertThat(foodsNutritional.get(0).getBrandName(), is("grilled fish"));
    assertEquals(3, foodsNutritional.size());

  }

}
Run Code Online (Sandbox Code Playgroud)

不幸的是,当我运行上述测试时,我收到如下异常:

org.mockito.exceptions.misusing.PotentialStubbingProblem: 
Strict stubbing argument mismatch. Please check:
 - this invocation of 'exchange' method:
    restTemplate.exchange(
    "https://trackapi.nutritionix.com/v2/natural/nutrients",
    POST,
    <com.application.meal.NutrientsBodyForRequest@3c486eb1,[]>,
    ParameterizedTypeReference<java.util.List<com.application.meal.FoodNutritional>>
);
    -> at com.application.meal.CaloriesService.receiveNutritionInformationFromAPI(CaloriesService.java:45)
 - has following stubbing(s) with different arguments:
    1. restTemplate.exchange(
    "https://trackapi.nutritionix.com/v2/natural/nutrients",
    POST,
    <com.application.meal.NutrientsBodyForRequest@afd496eb,[]>,
    ParameterizedTypeReference<java.util.List<com.application.meal.FoodNutritional>>
);
      -> at com.application.meal.CaloriesServiceTest.receiveNutritionInformationFromAPI(CaloriesServiceTest.java:69)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
Run Code Online (Sandbox Code Playgroud)

我的服务看起来像:

@Service
public class CaloriesService {

  private NutritionixHeader nutritionHeaderParam;

  private RestTemplate restTemplate;

  public CaloriesService(NutritionixHeader nutritionHeaderParam,
      RestTemplate restTemplate) {
    this.nutritionHeaderParam = nutritionHeaderParam;
    this.restTemplate = restTemplate;
  }

  private String prepareFoodNamesForRequest(DailyMeals dailyMeals) {
    return dailyMeals.getDailyMeals()
        .stream()
        .map(Meal::getMealIngredients)
        .flatMap(Collection::stream)
        .map(RecipeIngredient::getFoodName)
        .collect(Collectors.joining(" | "));
  }


  public List<FoodNutritional> receiveNutritionInformationFromAPI(DailyMeals dailyMeals) {

    String foodNamesForRequest = prepareFoodNamesForRequest(dailyMeals);

    HttpHeaders headers = new HttpHeaders();
    headers.set("x-app-id", nutritionHeaderParam.getNutritionixAppId());
    headers.set("x-app-key", nutritionHeaderParam.getNutritionixAppKey());

    HttpEntity<NutrientsBodyForRequest> request =
        new HttpEntity<>(NutrientsBodyForRequest.builder()
            .query(foodNamesForRequest)
            .build(), headers);

    ResponseEntity<List<FoodNutritional>> response =
        restTemplate
            .exchange(
                "https://trackapi.nutritionix.com/v2/natural/nutrients",
                HttpMethod.POST,
                request,
                new ParameterizedTypeReference<List<FoodNutritional>>() {
                });

    return response.getBody();
  }
}
Run Code Online (Sandbox Code Playgroud)

我的组件类如下:

DailyMeals.class

@Getter
@AllArgsConstructor
@EqualsAndHashCode
@Builder
class DailyMeals {

  @Id
  private Long id;
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "dailyMeals")
  List<Meal> dailyMeals;
}
Run Code Online (Sandbox Code Playgroud)

食品营养.class

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class FoodNutritional {

  @JsonProperty("food_name")
  private String foodName;

  @JsonProperty("brand_name")
  private String brandName;

  @JsonProperty("serving_qty")
  private Integer servingQuantity;

  @JsonProperty("serving_unit")
  private String servingUnit;

  @JsonProperty("serving_weight_grams")
  private String servingWeightGrams;

  @JsonProperty("nf_calories")
  private Double calories;

  @JsonProperty("nf_total_fat")
  private Double totalFat;

  @JsonProperty("nf_saturated_fat")
  private Double saturatedFat;

  @JsonProperty("nf_cholesterol")
  private Double cholesterol;

  @JsonProperty("nf_sodium")
  private Double sodium;

  @JsonProperty("nf_total_carbohydrate")
  private Double totalCarbohydrate;

  @JsonProperty("nf_dietary_fiber")
  private Double dietaryFiber;

  @JsonProperty("nf_sugars")
  private Double sugars;

  @JsonProperty("nf_protein")
  private Double protein;

  @JsonProperty("nf_potassium")
  private Double potassium;
}
Run Code Online (Sandbox Code Playgroud)

餐课

@AllArgsConstructor
@EqualsAndHashCode
@Getter
@Builder
@Entity
public class Meal {

  @Id
  @GeneratedValue
  private Long id;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "meal")
  List<RecipeIngredient> mealIngredients;
}
Run Code Online (Sandbox Code Playgroud)

编辑:

因为@Abhiram我正在附上并且 IntelliJ 抱怨您建议的代码段

错误

Edit2:经过更多思考,我认为我作为主体传递的对象的不同 HashCode 存在问题:NutrientsBodyForRequest@3c486eb1,[]并且NutrientsBodyForRequest@afd496eb,[],如果这可能是问题的根源,有什么建议吗?

我想要实现的就是修复此错误并检查测试模拟的服务是否会返回所需的结果。如果您提出有关如何实现目标的建议,我将不胜感激。

Mar*_*tin 5

经过和幸好合作@Abhiram,找到了解决办法。

我的模拟中缺少ArgumentMatcherfor 参数。

given(restTemplate
        .exchange(eq("https://trackapi.nutritionix.com/v2/natural/nutrients"),
            ArgumentMatchers.eq(HttpMethod.POST),
            ArgumentMatchers.any(),
            ArgumentMatchers.<ParameterizedTypeReference<List<FoodNutritional>>>any()))
        .willReturn(
            new ResponseEntity<List<FoodNutritional>>(
                foodsNutritionalStabData(),
                HttpStatus.OK));
Run Code Online (Sandbox Code Playgroud)

下面的方法解决了一个问题。非常感谢您的承诺。