Je ne parviens pas à essayer de tester un noeud final de repos qui reçoit une UserDetails
en tant que paramètre annoté avec @AuthenticationPrincipal
.
On dirait que l'instance d'utilisateur créée dans le scénario de test n'est pas utilisée, mais une tentative d'instanciation à l'aide du constructeur par défaut est effectuée à la place: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.andrucz.app.AppUserDetails]: No default constructor found;
Point de terminaison REST:
@RestController
@RequestMapping("/api/items")
class ItemEndpoint {
@Autowired
private ItemService itemService;
@RequestMapping(path = "/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Callable<ItemDto> getItemById(@PathVariable("id") String id, @AuthenticationPrincipal AppUserDetails userDetails) {
return () -> {
Item item = itemService.getItemById(id).orElseThrow(() -> new ResourceNotFoundException(id));
...
};
}
}
Classe de test:
public class ItemEndpointTests {
@InjectMocks
private ItemEndpoint itemEndpoint;
@Mock
private ItemService itemService;
private MockMvc mockMvc;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(itemEndpoint)
.build();
}
@Test
public void findItem() throws Exception {
when(itemService.getItemById("1")).thenReturn(Optional.of(new Item()));
mockMvc.perform(get("/api/items/1").with(user(new AppUserDetails(new User()))))
.andExpect(status().isOk());
}
}
Comment puis-je résoudre ce problème sans avoir à passer à webAppContextSetup
? Je voudrais écrire des tests avec un contrôle total des simulacres de service, donc j'utilise standaloneSetup
.
Cela peut être fait en injectant une HandlerMethodArgumentResolver
dans votre contexte Mock MVC ou votre configuration autonome. En supposant que votre @AuthenticationPrincipal
est de type ParticipantDetails
:
private HandlerMethodArgumentResolver putAuthenticationPrincipal = new HandlerMethodArgumentResolver() {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(ParticipantDetails.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return new ParticipantDetails(…);
}
};
Ce résolveur d'arguments peut gérer le type ParticipantDetails
et le crée tout seul, mais vous voyez que vous obtenez beaucoup de contexte. Plus tard, ce résolveur d'arguments est attaché à l'objet MVC simulé:
@BeforeMethod
public void beforeMethod() {
mockMvc = MockMvcBuilders
.standaloneSetup(…)
.setCustomArgumentResolvers(putAuthenticationPrincipal)
.build();
}
Ainsi, les arguments de votre méthode annotée @AuthenticationPrincipal
seront renseignés avec les détails de votre résolveur.
Pour une raison quelconque, la solution de Michael Piefel ne fonctionnant pas pour moi, j'en ai donc proposé une autre.
Tout d’abord, créez une classe de configuration abstraite:
@RunWith(SpringRunner.class)
@SpringBootTest
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
WithSecurityContextTestExecutionListener.class})
public abstract MockMvcTestPrototype {
@Autowired
protected WebApplicationContext context;
protected MockMvc mockMvc;
protected org.springframework.security.core.userdetails.User loggedUser;
@Before
public voivd setUp() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
loggedUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
Ensuite, vous pouvez écrire des tests comme celui-ci:
public class SomeTestClass extends MockMvcTestPrototype {
@Test
@WithUserDetails("[email protected]")
public void someTest() throws Exception {
mockMvc.
perform(get("/api/someService")
.withUser(user(loggedUser)))
.andExpect(status().isOk());
}
}
Et @AuthenticationPrincipal devrait injecter votre propre implémentation de classe User dans la méthode du contrôleur
public class SomeController {
...
@RequestMapping(method = POST, value = "/update")
public String update(UdateDto dto, @AuthenticationPrincipal CurrentUser user) {
...
user.getUser(); // works like a charm!
...
}
}
Je sais que la question est ancienne, mais pour les personnes qui recherchent toujours, ce qui a fonctionné pour moi pour écrire un test Spring Boot avec @AuthenticationPrincipal
(et cela peut ne pas fonctionner avec toutes les instances), annotait le test @WithMockUser("testuser1")
@Test
@WithMockUser("testuser1")
public void successfullyMockUser throws Exception {
mvc.perform(...));
}
Voici un lien vers la documentation Spring sur @WithMockUser
Ce n'est pas bien documenté, mais il existe un moyen d'injecter l'objet Authentication
en tant que paramètre de votre méthode MVC dans un standalone MockMvc . Si vous définissez Authentication
dans SecurityContextHolder
, le filtre SecurityContextHolderAwareRequestFilter
est généralement instancié par Spring Security et effectue l'injection de l'auth à votre place.
Vous devez simplement ajouter ce filtre à votre configuration MockMvc, comme ceci:
@Before
public void before() throws Exception {
SecurityContextHolder.getContext().setAuthentication(myAuthentication);
SecurityContextHolderAwareRequestFilter authInjector = new SecurityContextHolderAwareRequestFilter();
authInjector.afterPropertiesSet();
mvc = MockMvcBuilders.standaloneSetup(myController).addFilters(authInjector).build();
}