如何在单元测试期间通过 Thymeleaf 模板的身份验证
Posted
技术标签:
【中文标题】如何在单元测试期间通过 Thymeleaf 模板的身份验证【英文标题】:How to pass Authentication for Thymeleasf template during unit test 【发布时间】:2019-08-17 07:39:20 【问题描述】:我正在使用 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());
我的问题是,当我运行测试时,控制器上的 Authentication 值为 null。我已尝试添加安全上下文,但在正确处理时遇到了问题。我该如何解决这个问题
更新: 链接到示例存储库:https://github.com/dmbeer/thymeleaf-spring-security-test
【问题讨论】:
似乎还有其他事情发生。当我将您的测试和控制器复制到我的应用程序中时,测试完成得很好。我不得不对其进行调整以删除您的一些内部结构并添加一个 csrf 令牌,但您可以在 github.com/jzheaux/so-55365324 看到我的示例应用程序。 嗨@jzheaux,谢谢你,似乎如果我将你的Spring Boot版本更改为2.0.8.RELEASE,它会失败并出现同样的错误。我已经更新了 Spring Boot 版本的问题。 【参考方案1】:因此,在 @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());
```
【讨论】:
【参考方案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
会自动添加这些调用,因此您不必这样做。
您可以在the Spring Security repo 中查看它的示例。
【讨论】:
嗨@jzheaux,谢谢。但是,尝试了这种方法和文档中的方法。我仍然有很多问题,以至于现在两个测试都未能提交与以前相同的错误,并且 getthepage 未能找到视图。我已经用重现问题的项目链接更新了问题,谢谢 我已经下载了您的代码,并且很高兴,Authentication
已正确解析。如果您调试控制器方法,您会看到Authentication
实例确实存在。测试抛出 NPE 的原因是因为 userDetailsRepository 上没有模拟行为。
嗨 经过大量的试验和错误,我一切正常。 o 将其稍微更改为 bindToApplicationContext,以便它可以找到其他页面。最终解决方案如下。
太棒了!很高兴听到它。以上是关于如何在单元测试期间通过 Thymeleaf 模板的身份验证的主要内容,如果未能解决你的问题,请参考以下文章
如何对使用 thymeleaf 的安全控制器进行单元测试(不获取 TemplateProcessingException)?