【发布时间】:2019-02-10 12:04:52
【问题描述】:
首先,我在一个名为 RecipeController 的类中有以下端点方法:
@RequestMapping(value = {"/", "/recipes"})
public String listRecipes(Model model, Principal principal){
List<Recipe> recipes;
User user = (User)((UsernamePasswordAuthenticationToken)principal).getPrincipal();
User actualUser = userService.findByUsername(user.getUsername());
if(!model.containsAttribute("recipes")){
recipes = recipeService.findAll();
model.addAttribute("nullAndNonNullUserFavoriteRecipeList",
UtilityMethods.nullAndNonNullUserFavoriteRecipeList(recipes, actualUser.getFavoritedRecipes()));
model.addAttribute("recipes", recipes);
}
if(!model.containsAttribute("recipe")){
model.addAttribute("recipe", new Recipe());
}
model.addAttribute("categories", Category.values());
model.addAttribute("username", user.getUsername());
return "recipe/index";
}
正如您在上面看到的,该方法将 Principal 对象作为第二个参数。运行应用程序时,参数按预期指向非空对象。它包含有关当前在应用程序中登录的用户的信息。
我为 RecipeController 创建了一个名为 RecipeControllerTest 的测试类。此类包含一个名为 testListRecipes 的方法。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RecipeControllerTest{
@Mock
private RecipeService recipeService;
@Mock
private IngredientService ingredientService;
@Mock
private StepService stepService;
@Mock
private UserService userService;
@Mock
private UsernamePasswordAuthenticationToken principal;
private RecipeController recipeController;
private MockMvc mockMvc;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
recipeController = new RecipeController(recipeService,
ingredientService, stepService, userService);
mockMvc = MockMvcBuilders.standaloneSetup(recipeController).build();
}
@Test
public void testListRecipes() throws Exception {
User user = new User();
List<Recipe> recipes = new ArrayList<>();
Recipe recipe = new Recipe();
recipes.add(recipe);
when(principal.getPrincipal()).thenReturn(user);
when(userService.findByUsername(anyString()))
.thenReturn(user);
when(recipeService.findAll()).thenReturn(recipes);
mockMvc.perform(get("/recipes"))
.andExpect(status().isOk())
.andExpect(view().name("recipe/index"))
.andExpect(model().attributeExists("recipes"))
.andExpect(model().attributeExists("recipe"))
.andExpect(model().attributeExists("categories"))
.andExpect(model().attributeExists("username"));
verify(userService, times(1)).findByUsername(anyString());
verify(recipeService, times(1)).findAll();
}
}
正如您在第二个 sn-p 中看到的那样,我尝试使用 UsernamePasswordAuthenticationToken 实现来模拟测试类中的 Principal 对象。
当我运行测试时,我得到一个 NullPointerException,并且堆栈跟踪将我从代码的第一个 sn-p 指向以下行:
User user = (User)((UsernamePasswordAuthenticationToken)principal).getPrincipal();
作为参数传递给 listRecipes 方法的主体对象仍然为空,即使我尝试提供一个模拟对象。
有什么建议吗?
【问题讨论】:
-
首先,使用
@AuthenticationPrincipal并且不要在控制器中进行多次不安全的强制转换。然后只需构造一个真实的、实际的UserDetails实例并将其传递给控制器。使用 MockMvc 时,您需要阅读有关 Spring Security 集成的文档(需要将其添加到standaloneSetup),并且您应该在测试方法上使用类似@WithUserDetails的内容。 -
@chrylis 使用
@AuthenticationPrincipal User user作为第二个参数而不是Principal principal并在控制器方法中注释掉user变量对我来说是诀窍。User类实现了UserDetails接口,正如您在上面指定的那样。不再抛出异常。请在您的评论中创建一个答案,以便我将其标记为解决方案。
标签: java spring spring-boot spring-security mockito