Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その39 )( 貸出申請画面の作成10 )
概要
Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その38 )( 貸出申請画面の作成9 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 貸出申請画面の作成
- テストの作成 ( 2回目 )
- ちょっと長くなりすぎたので、全部で3回にします。
- 貸出申請画面の作成
参照したサイト・書籍
目次
- テスト作成対象クラスの変更
- printClassWhatNotMakeTest タスクのチェック対象外のパッケージを設定する
- Mail002Helper クラスのテストの作成
- UserHelper クラスのテストの作成
- LendingappFormValidator クラスのテストの作成
- LendingappController クラスのテストの作成
手順
テスト作成対象クラスの変更
- これまで Controller クラスと Controller クラスから呼び出す Service クラスはテスト内容が重複していたので、Controller クラスのみテストを作成するようにします。LendingappService クラスのテストは作成しません。
- LendingBookDto はテストするような実装もないのでテスト対象外にします。
printClassWhatNotMakeTest タスクのチェック対象外のパッケージを設定する
クラス名が Dto で終了するクラス全て、及び src/main/java/ksbysample/webapp/lending/web の下のクラス名が Service で終了するクラスをテスト作成対象外にします。
build.gradle を リンク先の内容 に変更します。
動作確認します。Gradle projects View から printClassWhatNotMakeTest タスクを実行します。
LendingBookDto、LendingappService が出力されないことが確認できます。
Mail002Helper クラスのテストの作成
src/main/java/ksbysample/webapp/lending/helper/mail の下の Mail002Helper.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/java/ksbysample/webapp/lending/helper/mail の下に Mail002HelperTest.java が作成されますので、リンク先の内容 に変更します。
テストを実行します。Mail002HelperTest クラスのクラス名の左側に表示されているアイコンをクリックしてコンテキストメニューを表示後「Run 'Mail002HelperTest' with Coverage」を選択します。
テストが成功することが確認できます。
UserHelper クラスのテストの作成
テストデータを修正し、suzuki hanako に ROLE_APPROVER 権限を付与します。src/test/resources/testdata/base の下の user_role.csv を リンク先の内容 に変更します。
src/main/java/ksbysample/webapp/lending/helper/user の下の UserHelper.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/java/ksbysample/webapp/lending/helper/user の下に UserHelperTest.java が作成されますので、リンク先の内容 に変更します。
テストを実行します。UserHelperTest クラスのクラス名の左側に表示されているアイコンをクリックしてコンテキストメニューを表示後「Run 'UserHelperTest' with Coverage」を選択します。
テストが成功することが確認できます。
LendingappFormValidator クラスのテストの作成
テストデータを作成します。src/test/resources/ksbysample/webapp/lending/web の下に lendingapp ディレクトリを作成します。
src/test/resources/ksbysample/webapp/lending/web/lendingapp の下に LendingappForm_001.yaml, LendingappForm_002.yaml, LendingappForm_003.yaml, LendingappForm_004.yaml を作成し、リンク先の内容 に変更します。
src/main/java/ksbysample/webapp/lending/web/lendingapp の下の LendingappFormValidator.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/java/ksbysample/webapp/lending/web/lendingapp の下に LendingappFormValidatorTest.java が作成されますので、リンク先の内容 に変更します。
テストを実行します。LendingappFormValidatorTest クラスのクラス名の左側に表示されているアイコンをクリックしてコンテキストメニューを表示後「Run 'LendingappFormValidatorTest' with Coverage」を選択します。
テストが成功することが確認できます。
LendingappController クラスのテストの作成
src/main/java/ksbysample/webapp/lending/web/lendingapp の下の LendingappController.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/java/ksbysample/webapp/lending/web/lendingapp の下に LendingappControllerTest.java が作成されます。
最初にテストの構成を決めます。src/test/java/ksbysample/webapp/lending/web/lendingapp の下の LendingappControllerTest.java を リンク先のその1の内容 に変更します。
テストデータを作成します。src/test/resources/ksbysample/webapp/lending/web/lendingapp の下に LendingappForm_005.yaml, LendingappForm_006.yaml, LendingappForm_007.yaml を作成し、リンク先の内容 に変更します。
src/test/resources/ksbysample/webapp/lending/web/lendingapp の下に testdata/001 ディレクトリを作成します。
src/test/resources/ksbysample/webapp/lending/web/lendingapp/testdata/001 の下に lending_app, lending_book テーブルの CSV ファイルを作成します。Database tool で以下のデータを lending_app, lending_book テーブルに登録します。
Database tool で出力する CSV ファイルの設定をします。Database tool で lending_app テーブルを選択後、コンテキストメニューを表示して「Save Data to File」-「Configure CSV Formats...」を選択します。
「CSV Formats」ダイアログが表示されます。以下の操作・設定をした後「OK」ボタンをクリックします。
Database tool で lending_app テーブルを選択後、コンテキストメニューを表示して「Save Data to File」-「Comma Separated Values (CSV)」を選択します。
「Save Data To File」ダイアログが表示されます。保存先のディレクトリに src/test/resources/ksbysample/webapp/lending/web/lendingapp/testdata/001 を選択し、ファイル名に "lending_app.csv" を入力した後「OK」ボタンをクリックします。
同じ手順で lending_book.csv も作成します。また lending_app.csv, lending_book.csv を出力したディレクトリに test-order.txt も作成します。
lending_app.csv, lending_book.csv, test-order.txt は リンク先の内容 になります。
検証用のデータを作成します。src/test/resources/ksbysample/webapp/lending/web/lendingapp の下に assertdata/001 ディレクトリを作成します。
src/test/resources/ksbysample/webapp/lending/web/lendingapp/testdata/001 の下の lending_app.csv, lending_book.csv, table-ordering.txt を src/test/resources/ksbysample/webapp/lending/web/lendingapp/assertdata/001 の下へコピーした後、リンク先の内容 に変更します。
src/test/resources/ksbysample/webapp/lending/web/lendingapp/assertdata/001 の下に message.txt を作成します。作成後、リンク先の内容 に変更します。
src/test/resources/ksbysample/webapp/lending/web/lendingapp/assertdata の下に 002 ディレクトリを作成します。
src/test/resources/ksbysample/webapp/lending/web/lendingapp/assertdata/001 の下の lending_app.csv, lending_book.csv, table-ordering.txt を src/test/resources/ksbysample/webapp/lending/web/lendingapp/assertdata/002 の下へコピーした後、リンク先の内容 に変更します。
テスト作成中に気づいた問題点を修正します。
テストを実装します。src/test/java/ksbysample/webapp/lending/web/lendingapp の下の LendingappControllerTest.java を リンク先のその2の内容 に変更します。
テストを実行します。LendingappControllerTest クラスのクラス名の左側に表示されているアイコンをクリックしてコンテキストメニューを表示後「Run 'LendingappControllerTest' with Coverage」を選択します。
テストが成功することが確認できます。
ソースコード
build.gradle
task printClassWhatNotMakeTest << { def srcDir = new File("src/main/java"); def excludePaths = [ "src/main/java/ksbysample/webapp/lending/Application.java" , "src/main/java/ksbysample/webapp/lending/config" , "src/main/java/ksbysample/webapp/lending/cookie" , "src/main/java/ksbysample/webapp/lending/dao" , "src/main/java/ksbysample/webapp/lending/entity" , "src/main/java/ksbysample/webapp/lending/exception" , "src/main/java/ksbysample/webapp/lending/helper/page/PagenationHelper.java" , "src/main/java/ksbysample/webapp/lending/security/LendingUser.java" , "src/main/java/ksbysample/webapp/lending/security/RoleAwareAuthenticationSuccessHandler.java" , "src/main/java/ksbysample/webapp/lending/service/calilapi/response" , "src/main/java/ksbysample/webapp/lending/service/file/BooklistCSVRecord.java" , "src/main/java/ksbysample/webapp/lending/service/openweathermapapi" , "src/main/java/ksbysample/webapp/lending/service/queue/InquiringStatusOfBookQueueMessage.java" , "src/main/java/ksbysample/webapp/lending/util/doma" , "src/main/java/ksbysample/webapp/lending/util/velocity/VelocityUtils.java" , "src/main/java/ksbysample/webapp/lending/web/.+/.+Service.java" , "src/main/java/ksbysample/webapp/lending/webapi/common/CommonWebApiResponse.java" , "src/main/java/ksbysample/webapp/lending/webapi/weather" ]; def excludeFileNamePatterns = [ ".*EventListener.java" , ".*Dto.java" , ".*Form.java" , ".*Values.java" ]; compareSrcAndTestDir(srcDir, excludePaths, excludeFileNamePatterns); }
- printClassWhatNotMakeTest タスクの excludePaths に以下の値を追加します。
, "src/main/java/ksbysample/webapp/lending/web/.+/.+Service.java"
- printClassWhatNotMakeTest タスクの excludeFileNamePatterns に以下の値を追加します。
, ".*Form.java"
Mail002HelperTest.java
package ksbysample.webapp.lending.helper.mail; import com.google.common.base.Charsets; import ksbysample.webapp.lending.Application; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import javax.mail.Message; import javax.mail.internet.MimeMessage; import java.io.File; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public class Mail002HelperTest { @Autowired private Mail002Helper mail002Helper; @Test public void testCreateMessage() throws Exception { MimeMessage message = mail002Helper.createMessage(new String[]{"test@sample.com", "sample@test.co.jp"}, 1L); assertThat(message.getRecipients(Message.RecipientType.TO)) .extracting(Object::toString) .containsOnly("test@sample.com", "sample@test.co.jp"); assertThat(message.getContent()) .isEqualTo(com.google.common.io.Files.toString( new File("src/test/resources/ksbysample/webapp/lending/helper/mail/assertdata/002/message.txt") , Charsets.UTF_8)); } }
user_role.csv
role_id,user_id,role 1,1,ROLE_USER 2,1,ROLE_ADMIN 3,1,ROLE_APPROVER 4,2,ROLE_USER 5,2,ROLE_APPROVER 6,3,ROLE_USER 7,4,ROLE_USER 8,5,ROLE_USER
5,2,ROLE_APPROVER
を追加し、それ以降の role_id を全て +1 します。
UserHelperTest.java
package ksbysample.webapp.lending.helper.user; import ksbysample.common.test.TestDataResource; import ksbysample.webapp.lending.Application; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public class UserHelperTest { @Rule @Autowired public TestDataResource testDataResource; @Autowired private UserHelper userHelper; @Test public void testGetApprovalMailAddrList() throws Exception { String[] approvalMailAddrList = userHelper.getApprovalMailAddrList(); assertThat(approvalMailAddrList).containsOnly("tanaka.taro@sample.com", "suzuki.hanako@test.co.jp"); } }
LendingappForm_001.yaml, LendingappForm_002.yaml, LendingappForm_003.yaml, LendingappForm_004.yaml, LendingappForm_005.yaml, LendingappForm_006.yaml, LendingappForm_007.yaml
■LendingappForm_001.yaml
!!ksbysample.webapp.lending.web.lendingapp.LendingappForm # 「一時保存」ボタンが押された場合 # 1件だけ lendingBookDtoList.lendingAppFlg = 1(申請する)、 lendingAppReason = (空) にしている lendingApp: lendingAppId: 1 status: 2 lendingUserId: 1 approvalUserId: version: 1 btn: temporarySave lendingBookDtoList: - lendingBookId: 1 isbn: 978-4-7741-6366-6 bookName: GitHub実践入門 lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: version: 1 - lendingBookId: 2 isbn: 978-4-7741-5377-3 bookName: JUnit実践入門 lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1 - lendingBookId: 3 isbn: 978-4-7973-8014-9 bookName: Java最強リファレンス lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1
■LendingappForm_002.yaml
!!ksbysample.webapp.lending.web.lendingapp.LendingappForm # 1つも「申請する」が選択されていない場合 lendingApp: lendingAppId: 1 status: 2 lendingUserId: 1 approvalUserId: version: 1 btn: apply lendingBookDtoList: - lendingBookId: 1 isbn: 978-4-7741-6366-6 bookName: GitHub実践入門 lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1 - lendingBookId: 2 isbn: 978-4-7741-5377-3 bookName: JUnit実践入門 lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1 - lendingBookId: 3 isbn: 978-4-7973-8014-9 bookName: Java最強リファレンス lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1
■LendingappForm_003.yaml
!!ksbysample.webapp.lending.web.lendingapp.LendingappForm # 「申請する」が選択されているが申請理由が入力されていない場合 lendingApp: lendingAppId: 1 status: 2 lendingUserId: 1 approvalUserId: version: 1 btn: apply lendingBookDtoList: - lendingBookId: 1 isbn: 978-4-7741-6366-6 bookName: GitHub実践入門 lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: version: 1 - lendingBookId: 2 isbn: 978-4-7741-5377-3 bookName: JUnit実践入門 lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1 - lendingBookId: 3 isbn: 978-4-7973-8014-9 bookName: Java最強リファレンス lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: version: 1
■LendingappForm_004.yaml
!!ksbysample.webapp.lending.web.lendingapp.LendingappForm # 「申請する」が選択されて申請理由も入力されている場合 lendingApp: lendingAppId: 1 status: 2 lendingUserId: 1 approvalUserId: version: 1 btn: apply lendingBookDtoList: - lendingBookId: 1 isbn: 978-4-7741-6366-6 bookName: GitHub実践入門 lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: 1 version: 1 - lendingBookId: 2 isbn: 978-4-7741-5377-3 bookName: JUnit実践入門 lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 version: 1 - lendingBookId: 3 isbn: 978-4-7973-8014-9 bookName: Java最強リファレンス lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1
■LendingappForm_005.yaml
!!ksbysample.webapp.lending.web.lendingapp.LendingappForm # lendingBookDtoList.lendingAppFlg のパターンエラー、lendingBookDtoList.lendingAppReason の最大文字数オーバーの場合 lendingApp: lendingAppId: 1 status: 2 lendingUserId: 1 approvalUserId: version: 1 btn: apply lendingBookDtoList: - lendingBookId: 1 isbn: 978-4-7741-6366-6 bookName: GitHub実践入門 lendingState: 蔵書あり lendingAppFlg: 2 lendingAppReason: パターンエラー version: 1 - lendingBookId: 2 isbn: 978-4-7741-5377-3 bookName: JUnit実践入門 lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 version: 1 - lendingBookId: 3 isbn: 978-4-7973-8014-9 bookName: Java最強リファレンス lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1
■LendingappForm_006.yaml
!!ksbysample.webapp.lending.web.lendingapp.LendingappForm # 「申請」ボタンクリック時の正常処理の試験用データ # 「蔵書あり」の4件中3件で「申請する」を選択し、申請理由を入力している lendingApp: lendingAppId: 105 status: 2 lendingUserId: 1 approvalUserId: version: 1 btn: apply lendingBookDtoList: - lendingBookId: 521 isbn: 978-4-7741-6366-6 bookName: GitHub実践入門 lendingState: 蔵書なし lendingAppFlg: lendingAppReason: version: 1 - lendingBookId: 522 isbn: 978-4-7741-5377-3 bookName: JUnit実践入門 lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: 開発で参照する為 version: 1 - lendingBookId: 523 isbn: 978-4-7973-8014-9 bookName: Java最強リファレンス lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: 開発で参照する為 version: 1 - lendingBookId: 524 isbn: 978-4-7973-4778-4 bookName: アジャイルソフトウェア開発の奥義 lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1 - lendingBookId: 525 isbn: 978-4-87311-704-1 bookName: Javaによる関数型プログラミング lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: 勉強の為 version: 1
■LendingappForm_007.yaml
!!ksbysample.webapp.lending.web.lendingapp.LendingappForm # 「一時保存」ボタンクリック時の正常処理の試験用データ # 「蔵書あり」の4件中3件で「申請する」を選択し、申請理由を入力している # * 1件は「申請する」を選択するが申請理由は入力しない # * 1件は「申請する」を選択しないが申請理由は入力する # * 1件は「申請する」を選択し、かつ申請理由も入力する lendingApp: lendingAppId: 105 status: 2 lendingUserId: 1 approvalUserId: version: 1 btn: temporarySave lendingBookDtoList: - lendingBookId: 521 isbn: 978-4-7741-6366-6 bookName: GitHub実践入門 lendingState: 蔵書なし lendingAppFlg: lendingAppReason: version: 1 - lendingBookId: 522 isbn: 978-4-7741-5377-3 bookName: JUnit実践入門 lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: version: 1 - lendingBookId: 523 isbn: 978-4-7973-8014-9 bookName: Java最強リファレンス lendingState: 蔵書あり lendingAppFlg: lendingAppReason: 検討中 version: 1 - lendingBookId: 524 isbn: 978-4-7973-4778-4 bookName: アジャイルソフトウェア開発の奥義 lendingState: 蔵書あり lendingAppFlg: 1 lendingAppReason: 勉強の為 version: 1 - lendingBookId: 525 isbn: 978-4-87311-704-1 bookName: Javaによる関数型プログラミング lendingState: 蔵書あり lendingAppFlg: lendingAppReason: version: 1
LendingappFormValidatorTest.java
package ksbysample.webapp.lending.web.lendingapp; import ksbysample.webapp.lending.Application; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.validation.Errors; import org.springframework.validation.FieldError; import org.springframework.validation.MapBindingResult; import org.yaml.snakeyaml.Yaml; import java.util.HashMap; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public class LendingappFormValidatorTest { // テストデータ private LendingappForm lendingappForm_001 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_001.yaml")); private LendingappForm lendingappForm_002 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_002.yaml")); private LendingappForm lendingappForm_003 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_003.yaml")); private LendingappForm lendingappForm_004 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_004.yaml")); @Autowired private LendingappFormValidator lendingappFormValidator; @Test public void testValidate_一時保存ボタン押下時は入力チェックは行われない() throws Exception { Errors errors = new MapBindingResult(new HashMap<String, String>(), ""); lendingappFormValidator.validate(lendingappForm_001, errors); assertThat(errors.hasFieldErrors()).isFalse(); } @Test public void testValidate_1つも申請するが選択されていない場合はエラーになる() throws Exception { Errors errors = new MapBindingResult(new HashMap<String, String>(), ""); lendingappFormValidator.validate(lendingappForm_002, errors); assertThat(errors.hasFieldErrors()).isTrue(); assertThat(errors.getFieldErrorCount()).isEqualTo(3); assertThat(errors.getFieldErrors()) .extracting(FieldError::getField) .containsOnly("lendingBookDtoList[0].lendingAppFlg" , "lendingBookDtoList[1].lendingAppFlg" , "lendingBookDtoList[2].lendingAppFlg"); } @Test public void testValidate_申請するを選択して申請理由を入力していない場合はエラーになる() throws Exception { Errors errors = new MapBindingResult(new HashMap<String, String>(), ""); lendingappFormValidator.validate(lendingappForm_003, errors); assertThat(errors.hasFieldErrors()).isTrue(); assertThat(errors.getFieldErrorCount()).isEqualTo(2); assertThat(errors.getFieldErrors()) .extracting(FieldError::getField) .containsOnly("lendingBookDtoList[0].lendingAppReason" , "lendingBookDtoList[2].lendingAppReason"); } @Test public void testValidate_申請するを選択して申請理由も入力している場合はエラーにならない() throws Exception { Errors errors = new MapBindingResult(new HashMap<String, String>(), ""); lendingappFormValidator.validate(lendingappForm_004, errors); assertThat(errors.hasFieldErrors()).isFalse(); } }
LendingappControllerTest.java
■その1
package ksbysample.webapp.lending.web.lendingapp; import ksbysample.webapp.lending.Application; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @RunWith(Enclosed.class) public class LendingappControllerTest { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請画面の初期表示のテスト_エラー処理 { @Test public void ログインしていなければ貸出申請画面は表示できない() throws Exception { } @Test public void lendingAppIdパラメータがなければエラーになる() throws Exception { } @Test public void lendingAppIdパラメータで指定された値が数値でなければエラーになる() throws Exception { } @Test public void lendingAppIdパラメータで指定されたデータが登録されていなければエラーになる() throws Exception { } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請画面の初期表示のテスト_正常処理 { @Test public void lendingAppIdパラメータで指定されたデータが登録されていれば貸出申請画面が表示される() throws Exception { } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請画面の入力チェックエラーのテスト { @Test public void 申請するが1つも選択されていない場合は入力チェックエラー() throws Exception { } @Test public void 申請するを選択して申請理由を入力していない場合は入力チェックエラー() throws Exception { } @Test public void 最大文字数オーバー_パターンエラーの場合は入力チェックエラー() throws Exception { } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請画面の正常処理時のテスト { @Test public void 申請ボタンをクリックした場合() throws Exception { } @Test public void 一時保存ボタンをクリックした場合() throws Exception { } } }
■その2
package ksbysample.webapp.lending.web.lendingapp; import com.google.common.base.Charsets; import ksbysample.common.test.*; import ksbysample.webapp.lending.Application; import ksbysample.webapp.lending.helper.message.MessagesPropertiesHelper; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.csv.CsvDataSet; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MvcResult; import org.yaml.snakeyaml.Yaml; import javax.mail.internet.MimeMessage; import javax.sql.DataSource; import java.io.File; import java.util.List; import static ksbysample.common.test.ErrorsResultMatchers.errors; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class LendingappControllerTest { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請画面の初期表示のテスト_エラー処理 { @Rule @Autowired public SecurityMockMvcResource mvc; @Autowired private MessagesPropertiesHelper messagesPropertiesHelper; @Test public void ログインしていなければ貸出申請画面は表示できない() throws Exception { mvc.noauth.perform(get("/lendingapp?lendingAppId=105")) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://localhost/")); } @Test public void lendingAppIdパラメータがなければエラーになる() throws Exception { MvcResult result = mvc.authTanakaTaro.perform(get("/lendingapp")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("error")) .andReturn(); String content = result.getResponse().getContentAsString(); assertThat(content).contains(messagesPropertiesHelper.getMessage("LendingappForm.lendingAppId.emptyerr", null)); } @Test public void lendingAppIdパラメータで指定された値が数値でなければエラーになる() throws Exception { MvcResult result = mvc.authTanakaTaro.perform(get("/lendingapp?lendingAppId=a")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("error")) .andReturn(); String content = result.getResponse().getContentAsString(); assertThat(content).contains(messagesPropertiesHelper.getMessage("LendingappForm.lendingAppId.emptyerr", null)); } @Test public void lendingAppIdパラメータで指定されたデータが登録されていなければエラーになる() throws Exception { MvcResult result = mvc.authTanakaTaro.perform(get("/lendingapp?lendingAppId=1")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("error")) .andReturn(); String content = result.getResponse().getContentAsString(); assertThat(content).contains(messagesPropertiesHelper.getMessage("LendingappForm.lendingApp.nodataerr", null)); } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請画面の初期表示のテスト_正常処理 { @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public TestDataLoaderResource testDataLoaderResource; @Rule @Autowired public SecurityMockMvcResource mvc; @Test @TestDataLoader("src/test/resources/ksbysample/webapp/lending/web/lendingapp/testdata/001") public void lendingAppIdパラメータで指定されたデータが登録されていれば貸出申請画面が表示される() throws Exception { MvcResult result = mvc.authTanakaTaro.perform(get("/lendingapp?lendingAppId=105")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("lendingapp/lendingapp")) .andExpect(model().hasNoErrors()) .andReturn(); String content = result.getResponse().getContentAsString(); assertThat(content) .contains("978-4-7741-6366-6").contains("GitHub実践入門") .contains("978-4-7973-8014-9").contains("Java最強リファレンス") .contains("978-4-87311-704-1").contains("Javaによる関数型プログラミング") .contains("978-4-7741-5377-3").contains("JUnit実践入門") .contains("978-4-7973-4778-4").contains("アジャイルソフトウェア開発の奥義"); } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請画面の入力チェックエラーのテスト { // テストデータ private LendingappForm lendingappForm_002 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_002.yaml")); private LendingappForm lendingappForm_003 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_003.yaml")); private LendingappForm lendingappForm_005 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_005.yaml")); @Rule @Autowired public SecurityMockMvcResource mvc; @Test public void 申請するが1つも選択されていない場合は入力チェックエラー() throws Exception { mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapp/apply", this.lendingappForm_002).with(csrf())) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("lendingapp/lendingapp")) .andExpect(model().hasErrors()) .andExpect(model().errorCount(4)) .andExpect(errors().hasGlobalError("lendingappForm", "LendingappForm.lendingBookDtoList.notExistApply")) .andExpect(errors().hasFieldError("lendingappForm", "lendingBookDtoList[0].lendingAppFlg", "")) .andExpect(errors().hasFieldError("lendingappForm", "lendingBookDtoList[1].lendingAppFlg", "")) .andExpect(errors().hasFieldError("lendingappForm", "lendingBookDtoList[2].lendingAppFlg", "")); } @Test public void 申請するを選択して申請理由を入力していない場合は入力チェックエラー() throws Exception { mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapp/apply", this.lendingappForm_003).with(csrf())) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("lendingapp/lendingapp")) .andExpect(model().hasErrors()) .andExpect(model().errorCount(3)) .andExpect(errors().hasGlobalError("lendingappForm", "LendingappForm.lendingBookDtoList.emptyReason")) .andExpect(errors().hasFieldError("lendingappForm", "lendingBookDtoList[0].lendingAppReason", "")) .andExpect(errors().hasFieldError("lendingappForm", "lendingBookDtoList[2].lendingAppReason", "")); } @Test public void 最大文字数オーバー_パターンエラーの場合は入力チェックエラー() throws Exception { mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapp/apply", this.lendingappForm_005).with(csrf())) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("lendingapp/lendingapp")) .andExpect(model().hasErrors()) .andExpect(model().errorCount(2)) .andExpect(errors().hasFieldError("lendingappForm", "lendingBookDtoList[0].lendingAppFlg", "Pattern")) .andExpect(errors().hasFieldError("lendingappForm", "lendingBookDtoList[1].lendingAppReason", "Size")); } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請画面の正常処理時のテスト { // テストデータ private LendingappForm lendingappForm_006 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_006.yaml")); private LendingappForm lendingappForm_007 = (LendingappForm) new Yaml().load(getClass().getResourceAsStream("LendingappForm_007.yaml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public TestDataLoaderResource testDataLoaderResource; @Autowired private DataSource dataSource; @Rule @Autowired public MailServerResource mailServerResource; @Rule @Autowired public SecurityMockMvcResource mvc; @Test @TestDataLoader("src/test/resources/ksbysample/webapp/lending/web/lendingapp/testdata/001") public void 申請ボタンをクリックした場合() throws Exception { // when ( Spock Framework のブロックの区分けが分かりやすかったので、同じ部分にコメントで付けてみました ) mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapp/apply", this.lendingappForm_006).with(csrf())) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("lendingapp/lendingapp")) .andExpect(model().hasNoErrors()); // then ( Spock Framework のブロックの区分けが分かりやすかったので、同じ部分にコメントで付けてみました ) // DB IDataSet dataSet = new CsvDataSet(new File("src/test/resources/ksbysample/webapp/lending/web/lendingapp/assertdata/001")); TableDataAssert tableDataAssert = new TableDataAssert(dataSet, dataSource); tableDataAssert.assertEquals("lending_app", new String[]{"lending_app_id", "approval_user_id"}); tableDataAssert.assertEquals("lending_book", new String[]{"lending_book_id", "lending_app_id", "lending_state", "lending_app_flg", "lending_app_reason", "approval_result", "approval_reason"}); // メール ( To にメールアドレスを2つ指定しているためメールが2通送信される ) assertThat(mailServerResource.getMessagesCount()).isEqualTo(2); List<MimeMessage> mimeMessageList = mailServerResource.getMessages(); for (MimeMessage mimeMessage : mimeMessageList) { assertThat(mimeMessage.getRecipients(javax.mail.Message.RecipientType.TO)) .extracting(Object::toString) .containsOnly("tanaka.taro@sample.com", "suzuki.hanako@test.co.jp"); assertThat(mimeMessage.getContent()) .isEqualTo(com.google.common.io.Files.toString( new File("src/test/resources/ksbysample/webapp/lending/web/lendingapp/assertdata/001/message.txt") , Charsets.UTF_8)); } } @Test @TestDataLoader("src/test/resources/ksbysample/webapp/lending/web/lendingapp/testdata/001") public void 一時保存ボタンをクリックした場合() throws Exception { mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapp/temporarySave", this.lendingappForm_007).with(csrf())) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("lendingapp/lendingapp")) .andExpect(model().hasNoErrors()); // then ( Spock Framework のブロックの区分けが分かりやすかったので、同じ部分にコメントで付けてみました ) // DB IDataSet dataSet = new CsvDataSet(new File("src/test/resources/ksbysample/webapp/lending/web/lendingapp/assertdata/002")); TableDataAssert tableDataAssert = new TableDataAssert(dataSet, dataSource); tableDataAssert.assertEquals("lending_app", new String[]{"lending_app_id", "approval_user_id"}); tableDataAssert.assertEquals("lending_book", new String[]{"lending_book_id", "lending_app_id", "lending_state", "lending_app_flg", "lending_app_reason", "approval_result", "approval_reason"}); } } }
testdata/001/lending_app.csv, lending_book.csv, test-order.txt
■lending_app.csv
lending_app_id,status,lending_user_id,approval_user_id,version 105,2,1,[null],1
■lending_book.csv
lending_book_id,lending_app_id,isbn,book_name,lending_state,lending_app_flg,lending_app_reason,approval_result,approval_reason,version 521,105,978-4-7741-6366-6,GitHub実践入門,蔵書なし,[null],[null],[null],[null],1 523,105,978-4-7973-8014-9,Java最強リファレンス,蔵書あり,[null],[null],[null],[null],1 525,105,978-4-87311-704-1,Javaによる関数型プログラミング,蔵書あり,[null],[null],[null],[null],1 522,105,978-4-7741-5377-3,JUnit実践入門,蔵書あり,[null],[null],[null],[null],1 524,105,978-4-7973-4778-4,アジャイルソフトウェア開発の奥義,蔵書あり,[null],[null],[null],[null],1
■test-order.txt
lending_app lending_book
assertdata/001/lending_app.csv, lending_book.csv, table-ordering.txt, message.txt
■lending_app.csv
lending_app_id,status,lending_user_id,approval_user_id,version 105,3,1,[null],2
■lending_book.csv
lending_book_id,lending_app_id,isbn,book_name,lending_state,lending_app_flg,lending_app_reason,approval_result,approval_reason,version 521,105,978-4-7741-6366-6,GitHub実践入門,蔵書なし,[null],[null],[null],[null],1 522,105,978-4-7741-5377-3,JUnit実践入門,蔵書あり,1,開発で参照する為,[null],[null],2 523,105,978-4-7973-8014-9,Java最強リファレンス,蔵書あり,1,開発で参照する為,[null],[null],2 524,105,978-4-7973-4778-4,アジャイルソフトウェア開発の奥義,蔵書あり,[null],[null],[null],[null],1 525,105,978-4-87311-704-1,Javaによる関数型プログラミング,蔵書あり,1,勉強の為,[null],[null],2
■table-ordering.txt
lending_app lending_book
■message.txt
貸出申請がありました。以下のURLから申請内容を確認してください。 http://localhost:8080/lendingapproval?lendingAppId=105
assertdata/002/lending_app.csv, lending_book.csv, table-ordering.txt, message.txt
■lending_app.csv
lending_app_id,status,lending_user_id,approval_user_id,version 105,2,1,[null],1
■lending_book.csv
lending_book_id,lending_app_id,isbn,book_name,lending_state,lending_app_flg,lending_app_reason,approval_result,approval_reason,version 521,105,978-4-7741-6366-6,GitHub実践入門,蔵書なし,[null],[null],[null],[null],2 522,105,978-4-7741-5377-3,JUnit実践入門,蔵書あり,1,[null],[null],[null],2 523,105,978-4-7973-8014-9,Java最強リファレンス,蔵書あり,[null],開発で参照する為,[null],[null],2 524,105,978-4-7973-4778-4,アジャイルソフトウェア開発の奥義,蔵書あり,1,勉強の為,[null],[null],2 525,105,978-4-87311-704-1,Javaによる関数型プログラミング,蔵書あり,[null],[null],[null],[null],2
■table-ordering.txt
lending_app lending_book
LendingappParamForm.java
package ksbysample.webapp.lending.web.lendingapp; import lombok.Data; import javax.validation.constraints.DecimalMax; import javax.validation.constraints.DecimalMin; import javax.validation.constraints.NotNull; @Data public class LendingappParamForm { @NotNull @DecimalMin(value = "1") @DecimalMax(value = "9223372036854775807") private Long lendingAppId; }
- lendingAppId に @NotNull アノテーションを付加します。
LendingappController.java
@RequestMapping public String index(@Validated LendingappParamForm lendingappParamForm , BindingResult bindingResult , LendingappForm lendingappForm , HttpServletResponse response) { if (bindingResult.hasErrors()) { if (lendingappParamForm.getLendingAppId() == null) { throw new WebApplicationRuntimeException( messagesPropertiesHelper.getMessage("LendingappForm.lendingAppId.emptyerr", null)); } else { throw new WebApplicationRuntimeException( messagesPropertiesHelper.getMessage("LendingappForm.lendingApp.nodataerr", null)); } } // 画面に表示するデータを取得する setDispData(lendingappParamForm.getLendingAppId(), lendingappForm); // 未申請の場合には LastLendingAppId Cookie に貸出申請ID をセットする if (StringUtils.equals(lendingappForm.getLendingApp().getStatus(), UNAPPLIED.getValue())) { CookieUtils.addCookie(CookieLastLendingAppId.class , response, String.valueOf(lendingappParamForm.getLendingAppId())); } return "lendingapp/lendingapp"; }
- index メソッドの引数を
BindingResult bindingResultForLendingappParamForm
→BindingResult bindingResult
へ変更します。 if (bindingResult.hasErrors()) { ... }
内の処理を lendingAppId パラメータが指定されているか否かで throw するメッセージを変えるようにします。
TestHelper.java
package ksbysample.common.test; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; public class TestHelper { /** * Formクラスをパラメータに持つMockHttpServletRequestBuilderクラスのインスタンスを生成する * * @param urlTemplate * @param form * @return * @throws IllegalAccessException */ public static MockHttpServletRequestBuilder postForm(String urlTemplate, Object form) throws IllegalAccessException { MockHttpServletRequestBuilder request = post(urlTemplate).contentType(MediaType.APPLICATION_FORM_URLENCODED); setParameterFromForm(request, form, null, null); return request; } /** * EntityクラスとFormクラスの値が同じかチェックする * * @param entity * @param form * @throws IllegalAccessException */ public static void assertEntityByForm(Object entity, Object form) throws IllegalAccessException { for (Field entityField : entity.getClass().getDeclaredFields()) { entityField.setAccessible(true); try { Field formField = form.getClass().getDeclaredField(entityField.getName()); formField.setAccessible(true); assertThat(entityField.get(entity), is(formField.get(form))); } catch (NoSuchFieldException ignored) {} } } /** * Form クラスのインスタンスのフィールド名と値を request にセットする * * @param request * @param form * @throws IllegalAccessException */ private static void setParameterFromForm(MockHttpServletRequestBuilder request, Object form, String fieldName, Integer arrayIndex) throws IllegalAccessException { for (Field field : form.getClass().getDeclaredFields()) { field.setAccessible(true); if (field.get(form) == null) { request = request.param(makeParameterName(fieldName, field.getName(), arrayIndex), ""); } else if (field.get(form) instanceof ArrayList<?>) { Integer i = 0; for (Object obj : (List<?>)field.get(form)) { if (isPrimitiveOrString(obj)) { request = request.param(makeParameterName(fieldName, field.getName(), i), obj.toString()); } else { setParameterFromForm(request, obj, makeParameterName(fieldName, field.getName(), arrayIndex), i); } i++; } } else if (isPrimitiveOrString(field.get(form))) { request = request.param(makeParameterName(fieldName, field.getName(), arrayIndex), field.get(form).toString()); } else { setParameterFromForm(request, field.get(form), makeParameterName(fieldName, field.getName(), arrayIndex), null); } } } /** * 指定されたオブジェクトがプリミティブ型かチェックする * * @param obj * @return */ private static boolean isPrimitiveOrString(Object obj) { boolean result = false; if (obj instanceof Byte) result = true; else if (obj instanceof Short) result = true; else if (obj instanceof Integer) result = true; else if (obj instanceof Long) result = true; else if (obj instanceof Character) result = true; else if (obj instanceof Float) result = true; else if (obj instanceof Double) result = true; else if (obj instanceof Boolean) result = true; else if (obj instanceof String) result = true; return result; } /** * MockHttpServletRequestBuilderクラスのインスタンス ( request ) にセットするパラメータ名を生成する * * @param rootFieldName * @param fieldName * @param arrayIndex * @return */ private static String makeParameterName(String rootFieldName, String fieldName, Integer arrayIndex) { StringBuilder sb = new StringBuilder(); if (rootFieldName != null) { sb.append(rootFieldName); if (arrayIndex != null) { sb.append("["); sb.append(arrayIndex); sb.append("]"); } sb.append("."); } sb.append(fieldName); return sb.toString(); } }
- Form クラス内に
String
あるいはList<String>
以外の型のフィールドが存在した場合に postForm メソッドで処理できない問題を解消するために以下の修正を行います。
履歴
2015/12/31
初版発行。