本文介绍了在单元测试Spring REST控制器时注入@AuthenticationPrincipal的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在尝试测试接收UserDetails作为用@AuthenticationPrincipal.

I am having trouble trying to test a REST endpoint that receives an UserDetails as a parameter annotated with @AuthenticationPrincipal.

似乎没有使用在测试场景中创建的用户实例,而是尝试使用默认构造函数实例化:org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.andrucz.app.AppUserDetails]: No default constructor found;

It seems like the user instance created in the test scenario is not being used, but an attempt to instantiate using default constructor is made instead: 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.

How can I solve that problem without having to switch to webAppContextSetup? I want to write tests having total control of service mocks, so I am using standaloneSetup.

推荐答案

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

This can be done by injection a HandlerMethodArgumentResolver into your Mock MVC context or standalone setup. Assuming your @AuthenticationPrincipal is of type 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(…);
    }
};

此参数解析程序可以处理类型ParticipantDetails,只是凭空创建它,但是您会看到很多上下文.稍后,此参数解析器将附加到模拟MVC对象:

This argument resolver can handle the type ParticipantDetails and just creates it out of thin air, but you see you get a lot of context. Later on, this argument resolver is attached to the mock MVC object:

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

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

This will result in your @AuthenticationPrincipal annotated method arguments to be populated with the details from your resolver.

这篇关于在单元测试Spring REST控制器时注入@AuthenticationPrincipal的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-29 20:41