Как правильно смоделировать объект Principal в Spring? - PullRequest
0 голосов
/ 10 февраля 2019

Прежде всего, у меня есть следующий метод конечной точки в классе с именем 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";
    }

Как вы можете видеть выше, метод принимает в качестве второго параметра a Основной объект.При запуске приложения параметр указывает на ненулевой объект, как и ожидалось.Он содержит информацию о пользователе, который в данный момент вошел в приложение.

Я создал тестовый класс для 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();
    }
}

. Как вы можете видеть из этого второго фрагмента, я пытался смоделировать объект Principal в тестовом классе., используя реализацию UsernamePasswordAuthenticationToken .

Когда я запускаю тест, я получаю NullPointerException , и трассировка стека указывает на следующую строку из первого фрагмента кода:

User user = (User)((UsernamePasswordAuthenticationToken)principal).getPrincipal();

Основной объект, переданный в качестве параметра методу listRecipes from, по-прежнему равен нулю, хотя я пытался предоставить фиктивный объект.

Есть предложения?

Ответы [ 3 ]

0 голосов
/ 11 февраля 2019

Spring MVC очень гибок с аргументами контроллера, что позволяет вам взять на себя большую часть ответственности за поиск информации в платформе и сосредоточиться на написании бизнес-кода.В этом конкретном случае, в то время как вы можете использовать Principal в качестве параметра метода, обычно гораздо лучше использовать фактический основной класс:

public String listRecipes(Model model, @AuthenticationPrincipal User user)

Чтобы фактически установить пользователя длятест, вам нужно работать с Spring Security, что означает добавление .apply(springSecurity()) в вашу настройку.(Кстати, такие сложности являются основной причиной, по которой я не люблю использовать standaloneSetup, поскольку она требует, чтобы вы не забыли продублировать ваши точные производственные настройки. Я рекомендую писать реальные модульные тесты и / или тесты с полным стеком.) Затем аннотируйтеваш тест с помощью @WithUserDetails и укажите имя пользователя тестового пользователя.

Наконец, как примечание стороны, этот шаблон контроллера может быть значительно упрощен с помощью Querydsl, так как Spring может внедрить Predicate, который объединяет всеатрибутов фильтра, которые вы просматриваете вручную, и затем вы можете передать этот предикат в репозиторий Spring Data.

0 голосов
/ 24 апреля 2019

Создайте класс, который реализует 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(.........;

}
0 голосов
/ 10 февраля 2019

Вы пытались использовать ...?

@Test
@WithMockUser(username = "my_principal")
public void testListRecipes() {
...
...