MockMvc @AuthenticationPrincipal NullPointerException
Пишу тесты для приложения на Spring Boot и Spring Mvc, используя MockMvc.
При попытке протестировать этот метод, где через аннотацию @AuthenticationPrincipal PersonDetails pd извлекается объект пользователя и затем на нем вызывается метод (pd.getPerson()), получаю NullPointerException.
А именно тестирую этот метод:
@GetMapping("/edit/{id}")
public String editPage(@PathVariable long id, Model model, @AuthenticationPrincipal PersonDetails pd) {
Task task = taskService.findById(id);
if (task.getAuthor() == null || task.getAuthor().getId() != pd.getPerson().getId()) {
throw new MismatchIdentifierException("ID автора не совпадает с Вашим. Похоже, что Вы не являетесь автором.");
}
model.addAttribute("task", task);
model.addAttribute("executors", personDetailsService.findAll());
return "editTask";
}
этим тестом:
@Test
@DisplayName("Get request at '/tasks/edit/5' should success")
@WithMockUser("testuser")
public void givenGetEditRequest_shouldReturnEditPage() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/tasks/edit/5").accept(MediaType.TEXT_HTML))
.andExpect(status().isOk()).andExpect(view().name("editTask")).andExpect(model().attribute("task", task))
.andExpect(model().attribute("executors", personDetailsService.findAll()));
}
и получаю ошибку:
Caused by: java.lang.NullPointerException: Cannot invoke "ru.terentyev.EffectiveMobile.security.PersonDetails.getPerson()" because "pd" is null
Хотя следующий метод, где тоже есть @AuthenticationPrincipal PersonDetails pd и где на нем тоже вызывается метод pd.getPerson() проходит тест успешно:
@PostMapping("/add")
public String addTask(@ModelAttribute @Valid Task task, BindingResult br, Model model
, @AuthenticationPrincipal PersonDetails pd) {
if (br.hasErrors()) {
model.addAttribute("executors", personDetailsService.findAll());
return "addTask";
}
task.setAuthor(pd.getPerson());
return showTask(taskService.save(task).getId(), 0, model, pd);
}
этим тестом:
@Test
@DisplayName("Post request at '/tasks/add' should receive new Task object")
@WithMockUser("testuser")
public void givenPostAddRequest_shouldReceiveNewObject() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/tasks/add").with(csrf()).contentType(MediaType.TEXT_PLAIN)
.flashAttr("task", task)).andExpect(status().isOk());
}
Пробовал добавлять with(csrf()) (хоть это вроде и не нужно) - не работает.
Часть тестового класса:
@SpringBootTest
//@WebMvcTest(TaskController.class)
@ContextConfiguration(classes = EffectiveMobileApplication.class)
@WebAppConfiguration
@AutoConfigureMockMvc
@ExtendWith(MockitoExtension.class)
public class TaskControllerTest {
@Autowired
private MockMvc mockMvc;
@InjectMocks
private TaskController taskController;
@Mock
private Model model;
@Spy
private List<Person> people;
@MockBean
private TaskService taskService;
@MockBean
private PersonDetailsService personDetailsService;
@Mock
private Task newCorrectTask;
@Spy
private Task task;
private Person person;
private Comment comment;
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
people = new ArrayList<Person>();
person = new Person();
person.setId(5l);
person.setName("Dmitriy");
person.setPassword("randompassword");
person.setEmail("[email protected]");
task = new Task();
task.setAuthor(person);
task.setExecutor(person);
task.setId(5l);
task.setStatus(Status.PROCESSING);
task.setPriority(Priority.HIGH);
Mockito.when(taskService.findById(5)).thenReturn(task);
Mockito.when(personDetailsService.findAll()).thenReturn(people);
}
// ...
}
Спасибо.
Ответы (1 шт):
После четырёхчасового перекапывания StackOverFlow, где чего только не советовали, и аннотации, и создание класса-наследника Principal, в нем переопределение getName() с произвольной строкой внутри, и кучу бесполезного хлама, ChatGPT решил всё за минуту: В тестовом классе сначала
@BeforeEach
public void setUp() {
UserDetails userDetails = User.withUsername("user")
.password("password")
.roles("USER")
.build();
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
А потом этот authenticationToken засылаем в тестируемый метод вместо замоканного principal:
mockMvc.perform(get("https://mysite.com/units")
.principal(authenticationToken))
.andExpect(status().isOk());