質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

Q&A

解決済

2回答

18027閲覧

Spring BootのControllerにおいてPOSTのテストをしたい

f97one

総合スコア29

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

0グッド

0クリップ

投稿2016/10/17 12:17

編集2016/10/17 13:51

###Spring BootのControllerにおいてPOSTのテストをしたい
現在Spring BootでWebアプリケーションの開発を行っています。
Controllerのユニットテストを書こうとしているところですが、POSTのテストでステータスコードが必ず403を返してくることに困っています。

###発生している問題

POSTはごく普通(?)にformからPOSTしています。

HTML

1<!DOCTYPE html> 2<html xmlns:th="http://www.thymeleaf.org"> 3<head> 4 <meta charset="UTF-8"/> 5</head> 6<body> 7 <form th:action="@{/ledger/create}" th:object="${newLedgerForm}" class="form-horizontal" method="POST"> 8 <table class="table table-condensed"> 9 <tr> 10 <td class="col-sg-1" style="border-style: none;">台帳名称</td> 11 <td class="col-sg-11" style="border-style: none;"> 12 <input type="text" id="ledgerName" name="ledgerName" 13 th:field="*{ledgerName}" th:errorClass="form-control" 14 th:disabled="${#httpServletRequest.remoteUser == null}" class="form-control" 15 value="hogeシステム改修"/> 16 <span th:if="${#fields.hasErrors('ledgerName')}" th:errors="*{ledgerName}" 17 class="help-block">Error</span> 18 </td> 19 </tr> 20 <tr> 21 <td class="col-sg-1" style="border-style: none;"></td> 22 <td class="col-sg-11" style="border-style: none;"> 23 <input type="hidden" id="openStatus" name="openStatus" th:field="*{openStatus}" value="1"/> 24 <input type="checkbox" id="publicLedger" name="publicLedger" th:field="*{publicLedger}" 25 th:disabled="${#httpServletRequest.remoteUser == null}"/> 26 <label for="publicLedger">参加ユーザー以外も参照できるようにする</label> 27 </td> 28 </tr> 29 <tr> 30 <td class="col-sg-1" style="border-style: none;"></td> 31 <td class="col-sg-11 form-inline" style="border-style: none;"> 32 <input type="submit" class="btn btn-default col-sg-4" id="returnBtn" name="returnBtn" value="戻る"/> 33 <input type="submit" class="btn btn-primary col-sg-4" id="addLedgerBtn" name="addLedgerBtn" 34 value="追加" th:disabled="${#httpServletRequest.remoteUser == null}"/> 35 </td> 36 </tr> 37 </table> 38 </form> 39</body> 40</html>

Contoller側は、以下のような処理としています。

Java

1@Controller 2@RequestMapping("ledger") 3public class LedgerController { 4 5 @ModelAttribute 6 NewLedgerForm setUpNewLedgerForm() { 7 return new NewLedgerForm(); 8 } 9 10 @RequestMapping(value = "/create", params = "addLedgerBtn", method = RequestMethod.POST) 11 public String addLedger(@Validated NewLedgerForm form, BindingResult result, Model model) { 12 // ログイン中ユーザー情報を取得 13 Users users = getUserState(); 14 15 String dest; 16 if (users == null) { 17 // ログインしていない場合はその場にとどまる 18 dest = "ledger/addLedger"; 19 } else { 20 // ログイン中の場合は、台帳データをDBに追加してトップ画面に移動 21 22 dest = "redirect:/"; 23 } 24 25 return dest; 26 } 27 28}

ControllerとHTMLで使用しているFormクラスは、以下のフィールドを持つPOJOです。

Java

1public class NewLedgerForm { 2 3 @NotNull 4 @Size(min = 1, max = 64) 5 private String ledgerName; 6 7 private Integer openStatus; 8 9 private boolean publicLedger; 10 11 // Getter、Setterは省略 12}

###該当のソースコード

以上のような処理を実装したので、JUnitによるテストコードを書きました。

Java

1@RunWith(SpringRunner.class) 2@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 3public class LedgerControllerTest { 4 5 @InjectMocks 6 private LedgerController mController; 7 @Autowired 8 private WebApplicationContext wac; 9 10 @Before 11 public void setUp() throws Exception { 12 if (mMvcMock == null) { 13 mMvcMock = MockMvcBuilders.webAppContextSetup(wac).apply(SecurityMockMvcConfigurers.springSecurity()) 14 .build(); 15 } 16 } 17 18 @Test 19 public void ログインしていないユーザーには台帳が追加できない() throws Exception { 20 ResultActions actions = mMvcMock.perform(MockMvcRequestBuilders.post("/ledger/create") 21 .contentType(MediaType.APPLICATION_FORM_URLENCODED) 22 .param("ledgerName", "ユーザーあり追加テスト用台帳") 23 .param("publicLedger", "checked") 24 .param("openStatus", "1") 25 ); 26 27 // 本来ならここでステータスコード200が得られるはずだが、なぜか403が返る 28 actions.andExpect(MockMvcResultMatchers.status().isOk()); 29 } 30 31 @Test 32 @WithMockUser(username = "user11") 33 public void ログインしている場合台帳が作成できる() throws Exception { 34 ResultActions actions = mMvcMock.perform(MockMvcRequestBuilders.post("/ledger/create") 35 .contentType(MediaType.APPLICATION_FORM_URLENCODED) 36 .param("ledgerName", "ユーザーあり追加テスト用台帳") 37 .param("publicLedger", "checked") 38 .param("openStatus", "1") 39 ); 40 41 // 本来ならここでステータスコード200が得られるはずだが、なぜか403が返る 42 actions.andExpect(MockMvcResultMatchers.status().isOk()); 43 } 44}

本来なら、両方ともステータスコード200となると思うのですが、なぜかソリッドに403を返されてしまい、テストが成立しない状況となっています。

なお、このアプリケーションを bootRun タスクで実行してブラウザをマニュアル操作すると想定どおりの挙動となるため、テストコードの実装が悪いのはほぼ間違いないと考えていますが、どこに原因があるのかがわからずにいます。

どなたかご教示いただきたく。

###補足情報(言語/FW/ツール等のバージョンなど)

  • Spring Boot 1.4.0.RELEASE を使用しています。
  • プロジェクトは Gradle で管理しています。
  • build.gradeldependencies は以下のとおりです。

build.gradle

1dependencies { 2 compile('org.springframework.boot:spring-boot-starter-data-jpa') 3 compile('org.springframework.boot:spring-boot-starter-thymeleaf') 4 compile('org.springframework.boot:spring-boot-starter-web') 5 compile('org.springframework.boot:spring-boot-starter-security') 6 compile('org.springframework.boot:spring-boot-configuration-processor') 7 compile('org.flywaydb:flyway-core') 8 compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4') 9 compile('org.webjars:bootstrap:3.3.7-1') 10 compile('org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16') 11 runtime('org.postgresql:postgresql') 12 runtime('com.h2database:h2') 13 providedCompile('org.projectlombok:lombok:1.16.10') 14 providedRuntime('org.springframework.boot:spring-boot-starter-tomcat') 15 testCompile('org.springframework.boot:spring-boot-starter-test') 16 testCompile('org.springframework.security:spring-security-test') 17}

###追加事項

SpringSecurityの設定は以下のとおりです。
ログインしていない状態で、登録ボタンのロックを解除してsubmitしたところ、ログイン状態に応じて処理の振り分けができていることまでは、確認できています。

Java

1@Configuration 2@EnableWebSecurity 3public class SecurityConfig extends WebSecurityConfigurerAdapter { 4 5 @Autowired 6 AuthorizedUsersService authorizedUserSvc; 7 8 @Override 9 protected void configure(HttpSecurity http) throws Exception { 10 http.authorizeRequests() 11 .antMatchers("/", "/loginForm", "/api/**", "/ledger/**").permitAll() 12 .antMatchers("/admin/**").hasRole(AppConstants.AUTHORITY_ADMIN) 13 .anyRequest().authenticated(); 14 15 http.formLogin() 16 .loginProcessingUrl("/login") 17 .loginPage("/loginForm") 18 .failureUrl("/loginForm?error") 19 .defaultSuccessUrl("/", true) 20 .usernameParameter("username") 21 .passwordParameter("password") 22 .permitAll(); 23 24 http.logout() 25 .logoutRequestMatcher(new AntPathRequestMatcher("/logout**")) 26 .logoutSuccessUrl("/"); 27 28 } 29 30 @Override 31 public void configure(WebSecurity web) throws Exception { 32 web.ignoring().antMatchers("/webjars/**", "/css/**"); 33 } 34 35 @Override 36 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 37 auth.authenticationProvider(createAuthProvider()); 38 } 39 40 private AuthenticationProvider createAuthProvider() { 41 DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); 42 provider.setUserDetailsService(authorizedUserSvc); 43 provider.setPasswordEncoder(new BCryptPasswordEncoder()); 44 45 return provider; 46 } 47}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

自己解決

Spring Securityの規定である「CSRF(Cross-Site Request Forgery)」トークンが生成されていない場合、POSTがFORBIDDENになる」が働いていました。

Java

1ResultActions actions = mMvcMock.perform(MockMvcRequestBuilders.post("/ledger/create") 2 .contentType(MediaType.APPLICATION_FORM_URLENCODED) 3 .with(SecurityMockMvcRequestPostProcessors.csrf()) 4 .param("ledgerName", "ユーザーあり追加テスト用台帳") 5 .param("publicLedger", "checked") 6 .param("openStatus", "1") 7);

.with(SecurityMockMvcRequestPostProcessors.csrf()) をつけることで、意図どおりのテストができました。

投稿2016/11/08 13:46

f97one

総合スコア29

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

SpringSecurityが有効になっていますが、テストコードから実行するときはSpringSecurityの認証はどのように対応しているでしょうか。

認証されていない状態でのPOSTなので403(FOBIDDEN)なのでは。

投稿2016/10/17 13:10

A-pZ

総合スコア12011

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

f97one

2016/11/08 13:42

Spring Securityの規定である「CSRF(Cross-Site Request Forgery)」トークンが生成されていない場合、POSTがFORBIDDENになる」が働いていました。 ```Java ResultActions actions = mMvcMock.perform(MockMvcRequestBuilders.post("/ledger/create") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .with(SecurityMockMvcRequestPostProcessors.csrf()) .param("ledgerName", "ユーザーあり追加テスト用台帳") .param("publicLedger", "checked") .param("openStatus", "1") ); ``` とすることで意図どおりの結果が得られることがわかりました。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問