はじめに
以前、MockMvcを使う記事を書きました。その後、Spring Securityを使ったアプリで MockMvcを上手く動作させられませんでした。SpringやJUnitのバージョンの違いか、色々な記事があるのですが、どれの方法を試しても駄目でした。それらの記事を参考にして試しながら、何とかテストコードが動かせるようになったので、記事にまとめます。
以前の記事でまとめたポイント
- JUnit5を使う為にアノテーションを差し替え
- テスト用コンフィグクラウを準備
以前の記事: SpringBootとJUnit5でMockMvcを使うには
// テストクラス
@ExctendWith(SpringExtension.class)
@WebMvcTest(XxxController.class)
// テスト用コンフィグクラス
@Configuration
@ComponentScan({"パッケージ名"})
今回の遭遇した問題
①アノテーション
例外が発生するので、どのアノテーションを使えばいいかを試行錯誤しました。
※記事末尾のサンプルをご参照下さい。
②HTTP403エラー Forbidden
Spring Securityを使うと、CSRF対策がデフォルトで有効になる為、HTTP403が発生。
MockMVCでリクエストする際、SecurityMockMvcRequestPostProcessors.csrf() を追加すればエラーを回避できる。
mockMvc.perform(post("http://localhost:8080/customers")
.contentType(MediaType.TEXT_HTML)
.with(SecurityMockMvcRequestPostProcessors.csrf())
...
③HTTP404エラー
リクエストURLをRequestMappingの記述部分しか書いていなかった為に発生。
※色々試していたアノテーションが原因かと勘違いして意外と時間がかかった。
java.lang.AssertionError: Range for response status value 404 expected:<SUCCESSFUL> but was:<CLIENT_ERROR>
④Thymeleafで出力しているログインユーザー情報の参照
これが一番難しかったです。
これは正にと思える @WithMockUser と言うアノテーションがあったので、設定するも、Thymeleafでログインユーザー情報が参照できず。
// エラーメッセージ
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'principal.user' of bean class
// @WithMockUser で設定したユーザーを指定して見たが駄目だった例
mockMvc.perform(get("http://localhost:8080/customers")
.contentType(MediaType.TEXT_HTML)
.with(SecurityMockMvcRequestPostProcessors.csrf())
.with(user("user1"))
org.springframework.security.core.userdetails.UserDetailsService インターフェースを実装したクラスを利用する事で参照できるように。
// まず、テストクラスのメンバ変数にサービスクラスを作成
@Autowired
private LoginUserDetailsService loginUserDetailsService;
// 次に、テストクラスのメソッドで、以下を実装
// インプリメントしたloadUserByUsername()メソッドでユーザー情報を取得
LoginUserDetails userDetails = (LoginUserDetails) loginUserDetailsService.loadUserByUsername("admin1");
// 取得したユーザー情報を渡す
mockMvc.perform(get("http://localhost:8080/customers")
.contentType(MediaType.TEXT_HTML)
.with(SecurityMockMvcRequestPostProcessors.csrf())
.with(user(userDetails))
⑤フォーム情報
MockHttpServletRequestBuilder.param()メソッドを利用するだけ。
コード
テストクラスに設定するアノテーションが変わりました。
前回必須だと思ったコンフィグクラスは不要でした。
@ExtendWith(SpringExtension.class)
@SpringBootTest
@TestInstance(Lifecycle.PER_CLASS)
@AutoConfigureMockMvc
public class CustomerControllerTest {
@Test
public void test() throws Exception {
LoginUserDetails userDetails = (LoginUserDetails) loginUserDetailsService.loadUserByUsername("admin1");
//@formatter:off
mockMvc.perform(post("http://localhost:8080/customers/create")
.contentType(MediaType.TEXT_HTML)
.with(SecurityMockMvcRequestPostProcessors.csrf())
.with(user(userDetails))
.param("firstName", "太郎")
.param("lastName", "山田")
)
.andExpect(SecurityMockMvcResultMatchers.authenticated())
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.redirectedUrl("/customers"))
.andReturn();
//@formatter:on
}
}
参考資料
- MockMvcを使ったControllerのUnitTestでCsrfトークンをRequestする
- Spring Security 使い方メモ CSRF
- Mocking Springframework Security Principal for testing Spring MVC Controllers
- Status expected:<200> but was:<404> in spring test
- Java Code Examples for org.springframework.security.authentication.TestingAuthenticationToken
- Spring Securityを使っているWebアプリのUnitテスト
- Testing Form posts through MockMVC