Java 在单元测试Spring REST控制器时注入@AuthenticationPrincipal
我在尝试测试一个REST端点时遇到问题,该端点接收一个Java 在单元测试Spring REST控制器时注入@AuthenticationPrincipal,java,spring,spring-mvc,spring-security,spring-test,Java,Spring,Spring Mvc,Spring Security,Spring Test,我在尝试测试一个REST端点时遇到问题,该端点接收一个UserDetails,作为用@AuthenticationPrincipal注释的参数。 似乎没有使用在测试场景中创建的用户实例,但尝试使用默认构造函数进行实例化:org.springframework.beans.beanInstationException:未能实例化[com.andrucz.app.AppUserDetails]:未找到默认构造函数 休息端点: @RestController @RequestMapping("
UserDetails
,作为用@AuthenticationPrincipal注释的参数。
似乎没有使用在测试场景中创建的用户实例,但尝试使用默认构造函数进行实例化:org.springframework.beans.beanInstationException:未能实例化[com.andrucz.app.AppUserDetails]:未找到默认构造函数代码>
休息端点:
@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));
...
};
}
}
如何解决该问题而不必切换到webAppContextSetup
?我想编写能够完全控制服务模拟的测试,所以我使用的是standaloneSetup。
这可以通过将HandlerMethodArgumentResolver
注入模拟MVC上下文或独立设置来完成。假设您的@AuthenticationPrincipal
类型为参与者详细信息
:
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(…);
}
};
这个参数解析器可以处理类型ParticipantDetails
,并且只是凭空创建它,但是您可以看到您获得了大量的上下文。稍后,此参数解析器将附加到模拟MVC对象:
@BeforeMethod
public void beforeMethod() {
mockMvc = MockMvcBuilders
.standaloneSetup(…)
.setCustomArgumentResolvers(putAuthenticationPrincipal)
.build();
}
这将导致您的@AuthenticationPrincipal
带注释的方法参数填充来自解析程序的详细信息。出于某种原因,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("someUser@app.com")
public void someTest() throws Exception {
mockMvc.
perform(get("/api/someService")
.withUser(user(loggedUser)))
.andExpect(status().isOk());
}
}
@Before
public void before() throws Exception {
SecurityContextHolder.getContext().setAuthentication(myAuthentication);
SecurityContextHolderAwareRequestFilter authInjector = new SecurityContextHolderAwareRequestFilter();
authInjector.afterPropertiesSet();
mvc = MockMvcBuilders.standaloneSetup(myController).addFilters(authInjector).build();
}
@AuthenticationPrincipal应该将您自己的用户类实现注入控制器方法
public class SomeController {
...
@RequestMapping(method = POST, value = "/update")
public String update(UdateDto dto, @AuthenticationPrincipal CurrentUser user) {
...
user.getUser(); // works like a charm!
...
}
}
我知道这个问题已经很老了,但对于那些仍在寻找答案的人来说,使用@AuthenticationPrincipal
编写Spring启动测试(这可能不适用于所有实例)的有效方法是使用mockuser(“testuser1”)注释测试。
对于@WithMockUser
上的Spring文档,虽然没有很好的文档记录,但有一种方法可以将身份验证
对象作为MVC方法的参数注入独立的MockMvc。如果在SecurityContextHolder
中设置身份验证
,则过滤器SecurityContextHolderAwareRequestFilter
通常由Spring Security实例化,并为您注入身份验证
您只需将该过滤器添加到MockMvc设置中,如下所示:
public class SomeTestClass extends MockMvcTestPrototype {
@Test
@WithUserDetails("someUser@app.com")
public void someTest() throws Exception {
mockMvc.
perform(get("/api/someService")
.withUser(user(loggedUser)))
.andExpect(status().isOk());
}
}
@Before
public void before() throws Exception {
SecurityContextHolder.getContext().setAuthentication(myAuthentication);
SecurityContextHolderAwareRequestFilter authInjector = new SecurityContextHolderAwareRequestFilter();
authInjector.afterPropertiesSet();
mvc = MockMvcBuilders.standaloneSetup(myController).addFilters(authInjector).build();
}
简化:
见:
您需要。因此,没有办法将StandaloneStup与身份验证结合使用?它在哪里说的?我不确定,但我如何才能获得所需的FilterChainProxy?您也可以使用webAppContextSetup,在使用@ContextConfiguration
保持对该上下文中bean的完全控制的同时,这是一种很好的方法,但要小心!默认情况下注册了约20个参数解析器,它们负责处理感兴趣的类型。在我的例子中,验证参数由ServletRequestMethodArgumentResolver处理,我的处理程序被忽略。新的Spring链接:
@ExtendWith(SpringExtension.class)
@SpringBootTest
@Transactional
@AutoConfigureMockMvc
public class ControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
@WithUserDetails(value = "user@gmail.com")
void get() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(URL))
.andExpect(status().isOk())
.andDo(print());
}