2016-07-12 60 views
8

我在嘗試測試接收UserDetails作爲參數@AuthenticationPrincipal註解的其餘端點時遇到問題。單元測試Spring REST控制器時注入@AuthenticationPrincipal

好像在測試場景中創建的用戶實例不被使用,但使用默認構造函數實例化一個嘗試,而不是:org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.andrucz.app.AppUserDetails]: No default constructor found;

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

測試類:

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

} 

如何解決該問題而無需切換到webAppContextSetup?我想寫測試有服務模擬的完全控制,所以我使用standaloneSetup

+0

您需要[請按照這些說明](http://docs.spring.io/spring-security /site/docs/4.0.x/reference/htmlsingle/#test-mockmvc)。 – OrangeDog

+0

因此,沒有辦法使用standaloneSetup與身份驗證相結合? – andrucz

+0

它在哪裏說的? – OrangeDog

回答

2

這可以通過在您的Mock MVC上下文或獨立設置中注入HandlerMethodArgumentResolver來完成。假設你的@AuthenticationPrincipalParticipantDetails類型:

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

這種說法解析器可以處理的類型ParticipantDetails,只是創建它憑空的,但你看你得到了很多方面的。後來,這種說法變壓器連接到模擬MVC對象:

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

這將導致你的@AuthenticationPrincipal註解的方法參數與您的解析器的細節來填充。

3

由於某種原因,Michael Piefel的解決方案對我不起作用,所以我想出了另一個解決方案。

首先,創建抽象配置類:

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

然後,你可以寫這樣的測試:

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

    } 
} 

而且@AuthenticationPrincipal要注入自己的用戶類實現到控制器的方法

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