REST 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));
...
};
}
}
テストクラス:
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.
を使用しています
これは、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(…);
}
};
この引数リゾルバーはParticipantDetails
型を処理でき、単純にそれを作成しますが、多くのコンテキストを取得していることがわかります。後で、この引数リゾルバーはモックMVCオブジェクトにアタッチされます。
@BeforeMethod
public void beforeMethod() {
mockMvc = MockMvcBuilders
.standaloneSetup(…)
.setCustomArgumentResolvers(putAuthenticationPrincipal)
.build();
}
これはあなたの@AuthenticationPrincipal
リゾルバからの詳細が入力される注釈付きメソッド引数。
どういうわけかマイケル・ピーフェルの解決策は私にはうまくいかなかったので、私は別の解決策を思いつきました。
まず、抽象構成クラスを作成します。
@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は独自のUserクラスの実装をコントローラーメソッドに注入する必要があります
public class SomeController {
...
@RequestMapping(method = POST, value = "/update")
public String update(UdateDto dto, @AuthenticationPrincipal CurrentUser user) {
...
user.getUser(); // works like a charm!
...
}
}
私は質問が古いことを知っていますが、まだ探している人にとって、_@AuthenticationPrincipal
_でSpring Bootテストを書くために私がうまくいったこと(そしてこれはすべてのインスタンスで機能しない可能性があります)がテストに注釈を付けていました@WithMockUser("testuser1")
_@Test
@WithMockUser("testuser1")
public void successfullyMockUser throws Exception {
mvc.perform(...));
}
_
リンクはこちら _@WithMockUser
_のSpringドキュメントへ
十分に文書化されていませんが、MVCメソッドのパラメーターとしてAuthentication
オブジェクトをstandalone MockMvcに挿入する方法があります。 Authentication
にSecurityContextHolder
を設定した場合、フィルターSecurityContextHolderAwareRequestFilter
は通常、Spring Securityによってインスタンス化され、authを挿入します。
次のように、MockMvcセットアップにそのフィルターを追加するだけです。
@Before
public void before() throws Exception {
SecurityContextHolder.getContext().setAuthentication(myAuthentication);
SecurityContextHolderAwareRequestFilter authInjector = new SecurityContextHolderAwareRequestFilter();
authInjector.afterPropertiesSet();
mvc = MockMvcBuilders.standaloneSetup(myController).addFilters(authInjector).build();
}