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

Я использую 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());
    }
}

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

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


person D. Beer    schedule 26.03.2019    source источник
comment
Похоже, что-то еще может происходить. Когда я копирую ваш тест и контроллер в свое приложение, тест завершается нормально. Мне пришлось настроить его, чтобы удалить некоторые из ваших внутренних компонентов и добавить токен csrf, но вы можете увидеть мое примерное приложение по адресу github.com/jzheaux/so-55365324.   -  person jzheaux    schedule 27.03.2019
comment
Привет @jzheaux, спасибо за это, кажется, что если я изменю вашу версию Spring Boot на 2.0.8.RELEASE так же, как моя, она выйдет из строя с той же ошибкой. Я обновил вопрос с версией весенней загрузки.   -  person D. Beer    schedule 28.03.2019


Ответы (2)


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

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

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

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

person jzheaux    schedule 28.03.2019
comment
Привет @jzheaux, спасибо за это. Однако, попробовав как этот подход, так и тот, который указан в документации. У меня все еще возникают такие проблемы, что теперь оба теста не отправляют с той же ошибкой, что и раньше, и getthepage с не удалось найти представление. Я обновил вопрос со ссылкой на проект, в котором воспроизводится проблема, спасибо - person D. Beer; 29.03.2019
comment
Я загрузил ваш код, и, к счастью, Authentication разрешен правильно. Если вы отлаживаете метод контроллера, вы увидите, что экземпляр Authentication действительно существует. Причина NPE, которую выдает тест, заключается в том, что в userDetailsRepository нет издевательства над поведением. - person jzheaux; 29.03.2019
comment
Привет После многих проб и ошибок у меня все работает. Пришлось немного изменить его на bindToApplicationContext, чтобы он нашел другие страницы. Окончательное решение ниже. - person D. Beer; 30.03.2019
comment
Потрясающие! Рад это слышать. - person jzheaux; 30.03.2019

Итак, после помощи от @jzheaux и соответствующей документации и руководства для webflux https://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());
    }
}```
person D. Beer    schedule 29.03.2019