2016-07-12 58 views
8

Ho problemi a provare a testare un endpoint di riposo che riceve un UserDetails come un parametro annotato con @AuthenticationPrincipal.Inject @AuthenticationPrincipal quando l'unità esegue il test di un controller Spring REST

Sembra come l'istanza utente creato nello scenario di prova non viene utilizzato, ma un tentativo di creare un'istanza utilizzando costruttore di default è fatta invece: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.andrucz.app.AppUserDetails]: No default constructor found;

REST endpoint:

@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 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()); 
    } 

} 

Come risolvere il problema senza passare a webAppContextSetup? Vorrei scrivere test con il controllo totale dei servizi di simulazione, quindi sto usando standaloneSetup.

+0

È necessario [seguire queste istruzioni] (http://docs.spring.io/spring-security /site/docs/4.0.x/reference/htmlsingle/#test-mockmvc). – OrangeDog

+0

Quindi non c'è modo di usare standaloneSetup combinato con l'autenticazione? – andrucz

+0

Dove lo dice? – OrangeDog

risposta

2

Questa operazione può essere eseguita mediante iniezione a HandlerMethodArgumentResolver nel contesto Mock MVC o in configurazione autonoma. Assumendo che il @AuthenticationPrincipal è di tipo ParticipantDetails:

private HandlerMethodArgumentResolver putPrincipal = 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(…); 
    } 
}; 

Questo argomento risolutore in grado di gestire il tipo ParticipantDetails ed appena lo crea dal nulla, ma si vede che si ottiene un sacco di contesto. In seguito, questo argomento resolver è collegato all'oggetto MVC finto:

@BeforeMethod 
public void beforeMethod() { 
    mockMvc = MockMvcBuilders 
      .standaloneSetup(…) 
      .setCustomArgumentResolvers(putAuthenticationPrincipal) 
      .build(); 
} 

Questo si tradurrà in vostra @AuthenticationPrincipal argomenti metodo annotato essere popolato con i dettagli del tuo resolver.

3

Per qualche motivo la soluzione di Michael Piefel non ha funzionato per me, quindi ne ho trovato un altro.

Prima di tutto, creare classe astratta configurazione:

@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(); 
    } 
} 

Quindi è possibile scrivere i test come questo:

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()); 

    } 
} 

E @AuthenticationPrincipal dovrebbe iniettare la propria implementazione classe User in metodo di controllo

public class SomeController { 
... 
    @RequestMapping(method = POST, value = "/update") 
    public String update(UdateDto dto, @AuthenticationPrincipal CurrentUser user) { 
     ... 
     user.getUser(); // works like a charm! 
     ... 
    } 
}