2016-07-12 59 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! 
     ... 
    } 
}