2016-07-12 1 views
8

@AuthenticationPrincipal으로 주석 된 매개 변수로 UserDetails을받는 나머지 끝점을 테스트하는 데 문제가 있습니다. 스프링 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)); 
      ... 
     }; 
    } 
} 

테스트 클래스 :

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

그래서 인증과 결합 된 standaloneSetup을 사용할 방법이 없습니까? – 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

마이클 피펠 (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("[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! 
     ... 
    } 
}