Как пройти аутентификацию для шаблона 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

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

jzheaux 27.03.2019 21:26

Привет @jzheaux, спасибо за это, кажется, если я изменю вашу версию Spring Boot на 2.0.8.RELEASE так же, как и моя, произойдет сбой с той же ошибкой. Я обновил вопрос с версией весенней загрузки.

D. Beer 28.03.2019 00:29
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
0
2
611
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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.

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

D. Beer 29.03.2019 00:33

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

jzheaux 29.03.2019 20:32

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

D. Beer 29.03.2019 23:44

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

jzheaux 30.03.2019 21:04

Итак, после помощи @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());
    }
}```

Другие вопросы по теме