Как пройти аутентификацию для шаблона Thymeleasf во время модульного тестирования

#spring-boot #spring-security #spring-webflux #spring-test

#весенняя загрузка #spring-безопасность #spring-webflux #spring-test

Вопрос:

Я использую Spring Boot 2.0.8.RELEASE. У меня есть контроллер, который имеет следующую конструкцию метода

 @PostMapping(value = "/profile/change-password", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public Mono<String> changePasswordSubmit(Authentication authentication, @RequestBody MultiValueMap<String, String> formData) {
  

И мой модульный тест, который выглядит как:

 @RunWith(SpringRunner.class)
@WebFluxTest(controllers = ChangePasswordController.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Import({ThymeleafAutoConfiguration.class, SpringSecurityContextUtils.class})
@WithMockUser(username = "test", password = "password")
public class ChangePasswordControllerTest {

    @Autowired
    WebTestClient webTestClient;
    @MockBean
    SpringUserDetailsRepository userDetailsRepository;

    @Autowired
    ChangePasswordController controller;

    @MockBean
    Authentication authentication;

    @Test
    public void addNewEntrySubmit() {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        formData.put("password1", Collections.singletonList("password"));
        formData.put("password2", Collections.singletonList("password"));

        webTestClient.post().uri("/profile/change-password").contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromFormData(formData)).exchange().expectStatus().isSeeOther().expectHeader().valueEquals(HttpHeaders.LOCATION, "/page/1");

//        verify(userDetailsRepository).updatePassword(captor.capture(), captor.capture());
        doNothing().when(userDetailsRepository).updatePassword(any(), any());
    }
}
  

Моя проблема в том, что при запуске теста значение аутентификации на контроллере равно null. Я пытался добавить контекст безопасности, но у меня возникли проблемы с его правильным использованием. Как мне это исправить

Обновление: Ссылка на пример репозитория: https://github.com/dmbeer/thymeleaf-spring-security-test

Комментарии:

1. Похоже, что может происходить что-то еще. Когда я копирую ваш тест и контроллер в свое приложение, тест завершается нормально. Мне пришлось настроить его, чтобы удалить некоторые из ваших внутренних компонентов и добавить токен csrf, но вы можете увидеть мой пример приложения на github.com/jzheaux/so-55365324 .

2. Привет @jzhaux, спасибо за это, похоже, что если я изменю вашу версию Spring Boot на 2.0.8.RELEASE, такую же, как у меня, она завершится с той же ошибкой. Я обновил вопрос с версией spring boot.

Ответ №1:

Итак, после справки от @jzhaux и соответствующей документации, а также руководства по webfluxhttps://docs.spring.io/spring-security/site/docs/5.0.11.RELEASE/reference/html/test-webflux.html

Мой модульный тест выглядит следующим образом:

     @RunWith(SpringRunner.class)
    @Import({ThymeleafAutoConfiguration.class})
    @WebFluxTest(controllers = ChangePasswordController.class)
    @WithMockUser(username = "test", authorities = {"ROLE_ADMIN"})
    @ContextConfiguration 
    public class ChangePasswordControllerTest {

    @Autowired
    ApplicationContext context;

    private WebTestClient webTestClient;

    @MockBean
    SpringUserDetailsRepository userDetailsRepository;

    @Captor
    private ArgumentCaptor<String> captor;

    @Before
    public void setUp() throws Exception {
        webTestClient = WebTestClient.bindToApplicationContext(context)
                .webFilter(new SecurityContextServerWebExchangeWebFilter())
                .apply(springSecurity())
                .configureClient()
                .build();
    }

    @Test
    public void getChangePasswordPageTest() {
        EntityExchangeResult<String> result = webTestClient
                .mutateWith(csrf())
                .get().uri("/profile/change-password")
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).returnResult();

        assertThat(result.getResponseBody(), stringContainsInOrder(Arrays.asList("<title>Change Password</title>",
                "<input type="password" class="form-control" id="password1" name="password1">")));
    }

    @Test
    public void addNewEntrySubmit() {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        formData.put("password1", Collections.singletonList("password"));
        formData.put("password2", Collections.singletonList("password"));

        given(userDetailsRepository.updatePassword(any(), any())).willReturn(Mono.empty());

        webTestClient.mutateWith(csrf()).post().uri("/profile/change-password").contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromFormData(formData)).exchange().expectStatus().isSeeOther().expectHeader().valueEquals(HttpHeaders.LOCATION, "/page/1");

        verify(userDetailsRepository).updatePassword(captor.capture(), captor.capture());
//        doNothing().when(userDetailsRepository).updatePassword(any(), any());
    }
}```
  

Ответ №2:

Перед загрузкой Spring 5.1.x необходимо вручную добавить конфигурацию фильтра безопасности Spring:

 WebTestClient webTestClient = WebTestClient
        .bindToController(new ChangedPasswordController())
        .webFilter(new SecurityContextServerWebExchangeWebFilter())
        .apply(springSecurity())
        .configureClient()
        .build();
  

В 5.1.x @WebFluxTest эти вызовы добавляются автоматически, поэтому вам не нужно.

Вы можете увидеть пример этого в репозитории Spring Security.

Комментарии:

1. Привет @jzhaux, спасибо за это. Однако, попробовав как этот подход, так и тот, что в документации. У меня все еще возникают проблемы, настолько серьезные, что теперь оба теста завершаются с ошибкой отправки с той же ошибкой, что и раньше, и getthepage с не удалось найти view. Я обновил вопрос ссылкой на проект, который воспроизводит проблему, спасибо

2. Я загрузил ваш код, и, к счастью, Authentication проблема решена правильно. Если вы выполняете отладку в методе контроллера, вы увидите, что Authentication экземпляр действительно существует. Причина NPE, с которой запускается тест, заключается в том, что в userDetailsRepository не высмеивается поведение.

3. Привет, после множества проб и ошибок у меня все работает. Пришлось немного изменить его на bindToApplicationContext, чтобы он находил другие страницы. Окончательное решение приведено ниже.

4. Потрясающе! Рад это слышать.