2016-07-12 12 views
8

Ich habe Probleme beim Testen eines Rest-Endpunkts, der einen als Parameter empfängt, der mit @AuthenticationPrincipal gekennzeichnet ist.Inject @AuthenticationPrincipal beim Unit-Testen eines Spring-REST-Controllers

scheint, wie die Benutzerinstanz im Testszenario erstellt nicht verwendet wird, sondern ein Versuch unter Verwendung von Standard-Konstruktor instanziiert wird stattdessen gemacht: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.andrucz.app.AppUserDetails]: No default constructor found;

REST-Endpunkt:

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

Prüfklasse:

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

} 

Wie kann ich dieses Problem lösen, ohne auf webAppContextSetup umschalten zu müssen? Ich würde gerne Tests schreiben, die die volle Kontrolle über Service-Mocks haben, also verwende ich standaloneSetup.

+0

Sie müssen [folgen Sie diesen Anweisungen] (http://docs.spring.io/spring-security /site/docs/4.0.x/reference/htmlsingle/#test-mockmvc). – OrangeDog

+0

Also gibt es keine Möglichkeit, StandaloneSetup in Kombination mit Authentifizierung zu verwenden? – andrucz

+0

Wo sagt es das? – OrangeDog

Antwort

2

Dies kann durch eine Injektion HandlerMethodArgumentResolver in Ihren Mock MVC Kontext oder Standalone-Setup erfolgen. Angenommen, Ihre @AuthenticationPrincipal ist vom Typ 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(…); 
    } 
}; 

Dieses Argument Resolver den Typ ParticipantDetails und es aus der Luft erzeugt nur umgehen kann, aber Sie sehen Sie eine Menge Kontext erhalten. Später wird dieses Argument Resolver auf das Mock MVC Objekt angehängt:

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

Dieses in Ihren @AuthenticationPrincipal kommentierten Methodenargumente führen mit den Details von Ihrem Resolver gefüllt werden.

3

Aus irgendeinem Grund funktionierte die Lösung von Michael Piefel nicht für mich, also habe ich mir eine andere ausgedacht.

Zunächst einmal schaffen abstrakte Konfigurationsklasse:

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

Dann können Sie Tests wie folgt schreiben:

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

    } 
} 

Und @AuthenticationPrincipal sollten Sie Ihre eigenen User-Klasse Implementierung in Controller-Methode injizieren

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