java - 如何在 Spring 中正确模拟 Principal 对象?
问题描述
首先,我在名为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();
}
}
正如您在第二个片段中看到的那样,我尝试使用UsernamePasswordAuthenticationToken实现来模拟测试类中的Principal对象。
当我运行测试时,我得到一个NullPointerException,并且堆栈跟踪将我指向第一个代码片段中的以下行:
User user = (User)((UsernamePasswordAuthenticationToken)principal).getPrincipal();
即使我尝试提供一个模拟对象,作为参数传递给listRecipes方法的主体对象仍然为空。
有什么建议么 ?
解决方案
Spring MVC 对控制器参数非常灵活,这让您可以将查找信息的大部分责任放到框架上,并专注于编写业务代码。在这种特殊情况下,虽然您可以将Principal
其用作方法参数,但使用实际的主体类通常要好得多:
public String listRecipes(Model model, @AuthenticationPrincipal User user)
要实际设置用户进行测试,您需要使用 Spring Security,这意味着添加.apply(springSecurity())
到您的设置中。(顺便说一下,像这样的复杂性是我不喜欢使用的主要原因standaloneSetup
,因为它要求您记住复制您的确切生产设置。我建议编写实际的单元测试和/或全栈测试。)然后用注释您的测试@WithUserDetails
并指定测试用户的用户名。
最后,作为旁注,可以使用 Querydsl 显着简化此控制器模式,因为 Spring 能够注入一个Predicate
结合了您手动查找的所有过滤器属性的 a,然后您可以将该谓词传递给 Spring Data 存储库.
推荐阅读
- haskell - 如何在没有编码的情况下在 Haskell 中进行 IO?
- dialogflow-es - 如何让代理使用Dialogflow在whatsapp中初始化聊天?
- angular - 使用 TypeORM / NestJS 连接到 MongoDB 更改流
- html - 如何通过引导程序在轮播中设置相等的高度?
- python - 在 ipyleaflet 中看不到 USGS WMS 层(leaflet.js for python)
- java - Azure 工具包错误无法执行运行配置
- python - 我无法打开用于在 python 中写入的文件,它给了我一个错误
- database - 如何在没有 localhost 127.0.0.1 的情况下单击 Laravel 中的链接?
- sql - 从 Teradata 中的范围创建行级数据
- php - 通过邮件 PHP 发送回显输出