首先,我在名为 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 对象作为第二个参数。运行应用程序时,参数按预期指向非null对象。它包含有关应用程序中当前登录用户的信息。
我为 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 方法的主体对象仍然为空。
有什么建议吗?
答案 0 :(得分:1)
Spring MVC的控制器参数非常灵活,这使您可以将查找信息的大部分责任放在框架上,并专注于编写业务代码。在这种情况下,虽然您可以 使用Principal
作为方法参数,但通常最好使用实际的主体类:
public String listRecipes(Model model, @AuthenticationPrincipal User user)
要实际设置用户进行测试,您需要使用Spring Security,这意味着将.apply(springSecurity())
添加到您的设置中。 (顺便说一下,这样的复杂性是我不喜欢使用standaloneSetup
的主要原因,因为它要求您记住复制精确的生产设置。我建议编写实际的单元测试和/或全栈测试。)然后用@WithUserDetails
注释测试,并指定测试用户的用户名。
最后,作为一个侧面说明,使用Querydsl可以大大简化此控制器模式,因为Spring可以注入一个Predicate
来合并您要手动查找的所有过滤器属性,然后您就可以将该谓词传递给Spring Data存储库。
答案 1 :(得分:0)
您尝试使用...吗?
@Test
@WithMockUser(username = "my_principal")
public void testListRecipes() {
...
答案 2 :(得分:0)
创建一个实现Principal
的类:
class PrincipalImpl implements Principal {
@Override
public String getName() {
return "XXXXXXX";
}
}
样本测试:
@Test
public void login() throws Exception {
Principal principal = new PrincipalImpl();
mockMvc.perform(get("/login").principal(principal)).andExpect(.........;
}