在单元测试Spring REST控制器时注入@AuthenticationPrincipal

and*_*ucz 17 java spring spring-mvc spring-security spring-test

我在尝试测试一个接收an UserDetails作为参数注释的休息端点时遇到了麻烦@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));
            ...
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

测试类:

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

}
Run Code Online (Sandbox Code Playgroud)

如何在不必切换的情况下解决该问题webAppContextSetup?我想编写完全控制服务模拟的测试,所以我正在使用standaloneSetup.

Mic*_*fel 7

这可以通过将a HandlerMethodArgumentResolver注入到Mock MVC上下文或独立设置中来完成.假设你@AuthenticationPrincipal的类型是ParticipantDetails:

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(…);
    }
};
Run Code Online (Sandbox Code Playgroud)

这个参数解析器可以处理类型ParticipantDetails并且只是凭空创建它,但是你会看到你获得了大量的上下文.稍后,这个参数解析器附加到模拟MVC对象:

@BeforeMethod
public void beforeMethod() {
    mockMvc = MockMvcBuilders
            .standaloneSetup(…)
            .setCustomArgumentResolvers(putAuthenticationPrincipal)
            .build();
}
Run Code Online (Sandbox Code Playgroud)

这将导致您的带@AuthenticationPrincipal注释的方法参数填充解析器中的详细信息.


Sam*_*Sam 6

我知道这个问题已经很老了,但对于仍在寻找的人来说,对我来说编写 Spring Boot 测试有用@AuthenticationPrincipal(这可能不适用于所有实例)的是注释测试@WithMockUser("testuser1")

@Test
@WithMockUser("testuser1")
public void successfullyMockUser throws Exception {
    mvc.perform(...));
}
Run Code Online (Sandbox Code Playgroud)

这是Spring 文档的链接@WithMockUser


pze*_*zko 5

出于某种原因,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();
    } 
}
Run Code Online (Sandbox Code Playgroud)

然后你可以写这样的测试:

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

    }
}
Run Code Online (Sandbox Code Playgroud)

并且@AuthenticationPrincipal 应该将您自己的 User 类实现注入到控制器方法中

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