♨️

Spring Boot+JUnit5+Spring SecurityでControllerのテストをしてハマったポイント

2021/01/18に公開

Spring Bootを使ってREST APIを構築していたところ、Controllerのテストでハマったのでその解決策を書きました。

テスト実行時にNullPointerになる

原因

Controllerのテストにはテストクラスに@SpringBootTest@AutoConfigureMockMvcアノテーションが必要でした。

GoodsControllerTest.java
@SpringBootTest
@AutoConfigureMockMvc
public GoodsControllerClass {
...
}

もしくは@WebMvcTestを使います。

GoodsControllerTest.java
@WebMvcTest
class GoodsControllerTest {
...
}

アノテーションの違いについて

SpringBootのJavadocを確認すると、@WebMvcTestを使った場合は以下のようになるようです。

  • MVCに関連する設定のみを適用する(@Controller, @ControllerAdvice等)
  • @Component, @Service, @Repositoryは除く
  • Spring SecurityやMockMvcは設定してくれる
  • Controller内で使うものは@MockBean等を使う必要がある

必要なものだけ、テスト時に設定してくれるアノテーションのため、ControllerやService, Repositoryを通しての結合テストがしたい場合は
@SpringBootTest@AutoConfigureMockMvcをあわせて使う必要があります。

なので、Controllerの単体テストであれば@WebMvcTestで良いです。というか、
テスト時に必要な設定も少なくできる分、単体テストの実行速度も向上できると考えられるので
その点でも単体テストでは@WebMvcTestを使うほうがベターな気がします。

MockMvcでのリクエストが401になってしまう

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /api/items
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {SPRING_SECURITY_SAVED_REQUEST=DefaultSavedRequest [http://localhost/api/items]}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 401
    Error message = Unauthorized
          Headers = [WWW-Authenticate:"Basic realm="Realm"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body =
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

原因

Spring Securityはデフォルトだとログインページ以外、アクセスに認証が必要な設定になっているようです。
【Spring Security はじめました 】#1 導入

検証:実際に画面からアクセスしてみるとログイン画面に戻される
認証設定確認GIF

xxxApplication.java(mainクラスがあるやつ)と同じ階層にWebSecurityConfigurerAdapterを継承したクラスを作成し、認証なしでアクセスできるページを設定します。

※import文省略

WebSecurityConfig.java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/api/items").permitAll().anyRequest().authenticated();
  }
}

これでテストを走らせると無事200でステータスが返ってくるようになりました。

ソース

こちら

参考資料

Discussion