2016-07-12 7 views
8

@AuthenticationPrincipalと注釈されたパラメータとしてUserDetailsを受け取る残りのエンドポイントをテストする際に問題があります。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)); 
      ... 
     }; 
    } 
} 

Testクラス:

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

スタンドアロンのセットアップと認証を組み合わせて使用​​する方法はありませんか? – andrucz

+0

それはどこですか? – OrangeDog

答えて

2

これは、Mock MVCコンテキストまたはスタンドアロンセットアップにHandlerMethodArgumentResolverを注入することで実行できます。

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を扱うことができ、ちょうど薄い空気からそれを作成しますが、あなたは、コンテキストの多くを得る参照してくださいと仮定すると、あなたの@AuthenticationPrincipalはタイプParticipantDetailsです。その後、この引数リゾルバはモックMVCオブジェクトに添付されています

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

これはあなたのリゾルバから詳細を移入するためにあなたの@AuthenticationPrincipal注釈付きメソッドの引数になります。

3

マイケル・ピーエルの解決策は私のためには機能しませんでしたので、別のものを考え出しました。

まず、抽象設定クラスを作成します。

@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! 
     ... 
    } 
} 
関連する問題