きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

SpringSecurityを使ったRestControllerのテストを行う

きり丸アドベントカレンダー2020の16記事目です。


自システムのデータをREST APIで公開すると、非同期で処理ができるようになるのでフロントのシステムをSPAにできたりします。第三者にデータを提供することもできます。

ぜひ、REST APIでシステムを公開することを考えてみましょう。

ちなみに、REST API自体は前に登壇した時の資料があるので、見ていただけるとうれしいです。

ゴール

環境

  • Java
    • 15
  • org.springframework.boot:spring-boot-starter-Web
    • 2.4.0
  • org.springframework.boot:spring-boot-starter-test
    • 2.4.0
  • org.springframework.boot:spring-boot-starter-security
    • 2.4.0
  • org.springframework.security:spring-security-test
    • 5.4.1
  • lombok

手順

テスト用の依存関係を追加する


基本的にコントローラ層のテストは@WebMvcTestでやるのがベストです。なおSpring SecurityでBASIC認証をかけていると、@SpringBootTestの使用時は正常終了しますが、@WebMvcTestの使用時はHttpStatus401:Unauthorized(未認証)が発生してしまいます。

それが発生しないようにSpring Securityのテスト用のライブラリとして、org.springframework.security:spring-security-testを依存関係に含めます。

ファイル名:build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compile 'org.springframework.boot:spring-boot-starter-security'
    compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
    testCompile 'org.springframework.security:spring-security-test:5.4.1' // 重要
}

RestAPIのコードを書く

基本的には@Controllerと同じように、@RestControllerを使用すればいいです。

次のコードでは「/v1/todos/{userId}」にGETメソッドでアクセスしたときに呼ばれます。{userId}は自由に変更できる項目です。「/v1/todos/admin」とした場合はパラメータに「admin」が渡されます。パラメータは@PathVariableで受け取れるようになります。

データがある場合は、HttpStatus:200にしたうえで、ResponseBodyに返却結果を返します。ない場合は、HttpStatus:404を返却します。

ファイル名:TodoRestController.java

@RequiredArgsConstructor
@RestController
@RequestMapping("/v1/todos")
public class TodoRestController {
  private final TodoRepository todoRepository;

  @GetMapping("/{userId}")
  public ResponseEntity<List<TodoDto>> get(@PathVariable String userId)  {

    final List<TodoDto> list = todoRepository.findList(userId);

    if (list.isEmpty()) {
      return ResponseEntity.notFound().build();
    } else {
      return ResponseEntity.ok(list);
    }
  }
}

データがあるときの返却結果例:

[{"id":1,"userId":"admin","action":"2"},{"id":2,"userId":"admin","action":"3"},{"id":3,"userId":"admin","action":"4"}]

テストコードを書く


@WebMvcTestにテスト対象のコントローラクラスを渡します。MockMvcを使用するので、@Autowiredを付与します。@MockBeanはテスト対象のクラスでDIしている項目に対して使用します。

Spring SecurityでBASIC認証を行っている場合、@WithMockUserを使用して認証している状態にする必要があります。

mockMvcでアクセスした結果を元に、HttpStatusやBodyを確認します。

ファイル名:TodoRestControllerTest.java

@WebMvcTest(TodoRestController.class)
@WithMockUser(value = "spring")
public class TodoRestControllerTests {
  @Autowired
  private MockMvc mockMvc;
  private final String rootUrl = "/v1/todos";

  @MockBean
  private TodoRepository todoRepository;

  @Test
  void success() throws Exception {
    // GIVEN
    when(todoRepository.findList("spring"))
        .thenReturn(List.of(
            TodoDto.builder().id(1).userId("spring").action("醤油").build(),
            TodoDto.builder().id(2).userId("spring").action("豆腐").build()

        ));

  // Language=json
  // TODO: テキストブロック機能はJava13の機能です
  // 文字エスケープしなくていいので見やすいです
  String expected = """
      [{
        "id": 1,
        "userId": "spring",
        "action": "醤油"
      },
        {
          "id": 2,
          "userId": "spring",
          "action": "豆腐"
        }
      ]""";

    this.mockMvc.perform(get(rootUrl + "/spring"))
        .andExpect(status().isOk())
        .andExpect(content().json(expected));
  }

  @Test
  void _404_not_found() throws Exception {
    when(todoRepository.findList("spring"))
        .thenReturn(List.of());

    this.mockMvc.perform(get(rootUrl + "/spring"))
        .andExpect(status().isNotFound())
    ;
  }
}

実際にアクセスするとき


BASIC認証を行う必要があります。

cURLでは次のどちらかでBASIC認証をしつつ、アクセスできます。 ※ あくまで私が用意したID:PWですので、BASIC認証する際は自分で用意したID:PWにしてください。

curl http://localhost:8080/v1/todos/admin -u admin:pass
curl http://admin:pass@localhost:8080/v1/todos/admin

ソースコード

アドベントカレンダー16日目。
github.com

終わりに

普段使用しているシステムはRestControllerを使用しているので、検証が非常に簡単に終わりました。しかし、Spring Securityと組み合わせたことはなかったので、@WithMockUserにたどり着くまでに時間かかってしまいました。昔であれば、secure=false等々を設定するだけでうまくいったようなのですが、新しくなってから設定方法が分からなかったです。

ある程度歴史があると、古い情報にばっかりアクセスしてしまうのでわかりづらいですね…。

Spring Securityが絡むと、いろいろと難易度が高くなるので情報を調べづらいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考記事

敬称略。

baeldung:Test a REST API with Java www.baeldung.com

公式リファレンス:Spring Security Reference docs.spring.io

類似記事

きり丸アドベントカレンダー2020 adventar.org

きり丸のHerokuページ
https://kirimaru-todoapp.herokuapp.com/

17日目のアドベントカレンダーの記事 https://nainaistar.hatenablog.com/entry/2020/12/17/083000nainaistar.hatenablog.com

f:id:nainaistar:20201109133010p:plain