【问题标题】:Mocking a void method in the Spring Framework (Mockito)在 Spring Framework (Mockito) 中模拟 void 方法
【发布时间】:2019-06-20 16:15:25
【问题描述】:

我正在为 Spring Web 应用程序编写集成测试,并且我已经到了需要模拟具有 void 返回类型的服务方法调用的步骤。我已经对一些方法进行了一些研究,但似乎没有一个是正确的方法。

我想做的是:

  • 当在 recipeService 上调用 save() 方法时,它应该保存配方

下面我将提供代码以及我已经尝试过的两种主要方法。如果有人可以提供帮助,那就太好了!

需要模拟的方法

@RequestMapping(path = "/recipes/add", method = RequestMethod.POST)
public String persistRecipe(@Valid Recipe recipe, BindingResult result, @RequestParam("image") MultipartFile photo, RedirectAttributes redirectAttributes) {
    if (result.hasErrors()) {
        redirectAttributes.addFlashAttribute("recipe", recipe);
        redirectAttributes.addFlashAttribute("flash",
                new FlashMessage("I think you missed something. Try again!", FlashMessage.Status.FAILURE));
        return "redirect:/recipes/add";
    }

    User user = getUser();
    recipe.setOwner(user);
    user.addFavorite(recipe);
    recipeService.save(recipe, photo);
    userService.save(user);
    redirectAttributes.addFlashAttribute("flash", new FlashMessage("The recipe has successfully been created", FlashMessage.Status.SUCCESS));

    return "redirect:/recipes";
}

需要调用的服务(保存方法)

@Service
public class RecipeServiceImpl implements RecipeService {

private final RecipeRepository recipes;

@Autowired
public RecipeServiceImpl(RecipeRepository recipes) {
    this.recipes = recipes;
}

@Override
public void save(Recipe recipe, byte[] photo) {
    recipe.setPhoto(photo);
    recipes.save(recipe);
}

@Override
public void save(Recipe recipe, MultipartFile photo) {
    try {
        recipe.setPhoto(photo.getBytes());
        recipes.save(recipe);
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

@Override
public Recipe findById(Long id) {
    Optional<Recipe> recipe = recipes.findById(id);
    if (recipe.isPresent()) {
        return recipe.get();
    }

    // TODO:drt - Create new exception to handle this
    throw new RuntimeException();
}

@Override
public Recipe findByName(String name) {
    return null;
}

@Override
public List<Recipe> findAll() {
    return (List<Recipe>) recipes.findAll();
}

@Override
public void deleteById(Long id) {
    recipes.deleteById(id);
}

}

尝试 1

@Test
@WithMockUser(value = "daniel")
public void createNewRecipeRedirects() throws Exception {
    User user = userBuilder();
    Recipe recipe = recipeBuilder(1L);
    recipe.setOwner(user);
    user.addFavorite(recipe);
    MockMultipartFile photo = new MockMultipartFile("image", "food.jpeg",
                    "image/png", "test image".getBytes());

    when(userService.findByUsername("daniel")).thenReturn(user);

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Recipe recipe1 = (Recipe) arguments[0];
                MultipartFile file = (MultipartFile) arguments[1];
                recipe1.setPhoto(file.getBytes());

            }
            return null;
        }
    }).when(recipeService).save(any(Recipe.class), any(MultipartFile.class));

    mockMvc.perform(post("/recipes/add"))
            .andExpect(redirectedUrl("/recipes"))
            .andExpect(flash().attributeExists("flash"));


}

尝试 2

@Test
@WithMockUser(value = "daniel")
public void createNewRecipeRedirects() throws Exception {
    List<Recipe> recipes = recipeListBuilder();
    List<User> users = new ArrayList<>();
    User user = userBuilder();
    Recipe recipe = recipeBuilder(1L);
    recipe.setOwner(user);
    user.addFavorite(recipe);
    MockMultipartFile photo = new MockMultipartFile("image", "food.jpeg",
                    "image/png", "test image".getBytes());

    when(userService.findByUsername("daniel")).thenReturn(user);

    doAnswer(answer -> {
        recipe.setPhoto(photo.getBytes());
        recipes.add(recipe);
        return true;
    }).when(recipeService).save(any(Recipe.class), any(MultipartFile.class));

    doAnswer(answer -> {
        users.add(user);
        return true;
    }).when(userService).save(any(User.class));

    mockMvc.perform(post("/recipes/add"))
            .andExpect(redirectedUrl("/recipes"))
            .andExpect(flash().attributeExists("flash"));

    assertEquals(3, recipes.size());
    assertEquals(1, users.size());
}

到目前为止完成的测试代码

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
public class RecipeControllerTests {

private MockMvc mockMvc;

@Mock
private RecipeService recipeService;

@Mock
private UserService userService;

@Mock
private IngredientService ingredientService;

@Autowired
WebApplicationContext wac;

@InjectMocks
private RecipeController recipeController;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    mockMvc = MockMvcBuilders.webAppContextSetup(wac).apply(springSecurity()).build();
}

/**
 * Tests for index pages / & /recipes
 */
@Test
@WithUserDetails(value = "daniel")
public void indexPageLoads() throws Exception {
    List<Recipe> recipes = recipeListBuilder();
    List<Ingredient> ingredients = ingredientsListBuilder();

    when(recipeService.findAll()).thenReturn(recipes);
    when(ingredientService.findAll()).thenReturn(ingredients);
    when(userService.findByUsername("daniel")).thenReturn(userBuilder());

    mockMvc.perform(get("/recipes"))
            .andExpect(model().attributeExists("recipes", "ingredients", "favs"))
            .andExpect(status().isOk());
}

/**
 * Tests for page /recipes/add
 */
@Test
@WithMockUser
public void addRecipePageLoads() throws Exception {
    mockMvc.perform(get("/recipes/add"))
            .andExpect(model().attributeExists("task", "buttonAction", "action", "photo", "recipe"))
            .andExpect(status().isOk());
}

@Test
@WithUserDetails("daniel")
public void createNewRecipeRedirects() throws Exception {
    User user = userBuilder();
    Recipe recipe = recipeBuilder(1L);
    recipe.setOwner(user);
    user.addFavorite(recipe);
    MultipartFile photo = new MockMultipartFile("image", "food.jpeg",
            "image/jpeg", "dummy content file".getBytes());

    when(userService.findByUsername("daniel")).thenReturn(user);
    verify(recipeService, times(1)).save(recipe, photo);
    verify(userService, times(1)).save(user);


    mockMvc.perform(post("/recipes/add"))
            .andExpect(redirectedUrl("/recipes"))
            .andExpect(flash().attributeExists("flash"));


}


private User userBuilder() {
    User user = new User();
    user.setFavorites(recipeListBuilder());
    user.setId(1L);
    user.setRoles(new String[]{"ROLE_USER", "ROLE_ADMIN"});
    user.setUsername("daniel");
    user.setPassword("password");

    return user;
}

private List<Recipe> recipeListBuilder() {
    List<Recipe> recipes =  new ArrayList<>();
    recipes.add(recipeBuilder(1L));
    recipes.add(recipeBuilder(2L));

    return recipes;
}

private List<Ingredient> ingredientsListBuilder() {
    List<Ingredient> ingredients = new ArrayList<>();
    ingredients.add(ingredientBuilder());

    return ingredients;
}

private Ingredient ingredientBuilder() {
    Ingredient ingredient = new Ingredient();
    ingredient.setCondition("good");
    ingredient.setName("test ing");
    ingredient.setQuantity(1);
    ingredient.setId(1L);

    return ingredient;
}

private Recipe recipeBuilder(Long id) {
    Recipe recipe = new Recipe();
    recipe.setName("Test recipe");
    recipe.setDescription("Test Description");
    recipe.setId(id);
    recipe.setCategory(Category.ALL_CATEGORIES);
    recipe.setCookTime(10);
    recipe.setPrepTime(10);
    recipe.addIngredient(ingredientBuilder());

    return recipe;
}
}

【问题讨论】:

  • 如果你有一个返回void的方法并且你想模拟拥有这个方法的类的对象,典型的做法是验证这个方法是在模拟对象上调用的Mockito::verify.
  • 另外,你没有说你期望发生什么,而是发生了什么。
  • 对不起,我应该更明确一点。我想模拟服务调用 save() ,它接受一个配方对象和一个多部分文件。
  • 只需使用Mockito::verify 来测试您的逻辑是否在您的模拟上调用save 方法,因为您可能不想在这里测试保存逻辑。
  • 我刚刚尝试过并收到错误消息:“需要但未调用:recipeService.save(com.danielturato.recipe.recipe.Recipe@1f1574c, org.springframework.mock.web. MockMultipartFile@16c9e378 ); -> at com.danielturato.recipe.recipe.RecipeControllerTests.createNewRecipeRedirects(RecipeControllerTests.java:112) 实际上,与此模拟的交互为零。”

标签: java spring testing mocking mockito


【解决方案1】:

尝试Mockito.doNothing():它基本上告诉 Mockito 在调用模拟对象中的方法时什么都不做:

Mockito.doNothing().when(recipeService).save(any(Recipe.class), any(MultipartFile.class));

【讨论】:

    【解决方案2】:

    如果您要模拟保存方法,我会使用“do...”之一 Docs 当然,这表明您的方法在某处有副作用。

    如果您想确保调用某个方法,您可以使用其他答案中提到的“验证”。

    一般来说,模拟允许您将某些协作/功能替换为受测试控制的版本,其中验证允许检查发生(或未发生)的事情

    【讨论】:

      【解决方案3】:

      如果您有一些想要进行单元测试的逻辑,并且该逻辑调用了您想要模拟的其他组件的方法,并且其中一些方法返回 void - 测试您的逻辑的典型方法是验证您的逻辑实际上调用了模拟对象的void 方法。您可以使用Mockito::verify 来实现这一点:

       Mockito.verify(recipeService, Mockito.times(1)).save(any(Recipe.class), any(MultipartFile.class));
      

      通过这种方式,您可以测试 persistRecipe() 方法的逻辑实际上在您的模拟对象上调用了所需的方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-09-21
        • 2011-01-17
        • 1970-01-01
        • 2021-02-04
        • 1970-01-01
        相关资源
        最近更新 更多