Spring Boot + npm + Geb で入力フォームを作ってテストする ( その61 )( 確認画面を作成する4 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その60 )( 確認画面を作成する3 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 確認画面の作成
- サーバ側のテストを作成します。2回に分けます。
参照したサイト・書籍
目次
- pmd-project-rulesets.xml を変更する
- SessionData2ConfirmFormTypeMap クラスのテストを作成する
- SessionData2InquiryDataTypeMap クラスのテストを作成する
- InquiryMailHelper クラスのテストを作成する
手順
pmd-project-rulesets.xml を変更する
ksbysample.webapp.bootnpmgeb.helper.mail.InquiryMailHelper#generateTextUsingVelocity の Map<String, Object> model = new HashMap<>();
の部分に対して PMD が UseConcurrentHashMap の警告を出してくるのですが、マルチスレッドでアクセスする部分ではないのと、このアプリでは ConcurrentHashMap の使用を考慮する必要はないので、UseConcurrentHashMap の Rule を除外します。
config/pmd/pmd-project-rulesets.xml の以下の点を変更します。
<rule ref="category/java/multithreading.xml"> <exclude name="UseConcurrentHashMap"/> </rule>
<exclude name="UseConcurrentHashMap"/>
を追加します。
SessionData2ConfirmFormTypeMap クラスのテストを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/mapper/SessionData2ConfirmFormTypeMap.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。
src/test/groovy/ksbysample/webapp/bootnpmgeb/mapper/SessionData2ConfirmFormTypeMapTest.groovy が新規作成されるので、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.mapper import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper import ksbysample.webapp.bootnpmgeb.session.SessionData import ksbysample.webapp.bootnpmgeb.values.JobValues import ksbysample.webapp.bootnpmgeb.values.SexValues import ksbysample.webapp.bootnpmgeb.values.Type1Values import ksbysample.webapp.bootnpmgeb.values.Type2Values import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput02Form import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput03Form import org.apache.commons.lang3.StringUtils import org.modelmapper.ModelMapper import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification import java.util.stream.Collectors @SpringBootTest class SessionData2ConfirmFormTypeMapTest extends Specification { @Autowired private ModelMapper modelMapper @Autowired private SurveyOptionsHelper soh InquiryInput01Form inquiryInput01Form InquiryInput02Form inquiryInput02Form InquiryInput03Form inquiryInput03Form SessionData sessionData def setup() { inquiryInput01Form = new InquiryInput01Form( lastname: "田中" , firstname: "太郎" , lastkana: "たなか" , firstkana: "たろう" , sex: SexValues.MALE.value , age: "30" , job: JobValues.EMPLOYEE.value ) inquiryInput02Form = new InquiryInput02Form( zipcode1: "100" , zipcode2: "0005" , address: "東京都千代田区丸の内" , tel1: "03" , tel2: "1234" , tel3: "5678" , email: "test@sample.co.jp" ) inquiryInput03Form = new InquiryInput03Form( type1: Type1Values.PRODUCT.value , type2: [Type2Values.CATALOGUE.value , Type2Values.ESTIMATE.value , Type2Values.OTHER.value] , inquiry: "これは\r\nテスト\r\nです。" , survey: soh.selectItemList("survey").stream() .map { SurveyOptions surveyOptions -> surveyOptions.itemValue } .collect() ) sessionData = new SessionData( inquiryInput01Form: inquiryInput01Form , inquiryInput02Form: inquiryInput02Form , inquiryInput03Form: inquiryInput03Form ) } def "全ての項目に値がセットされている場合のテスト"() { setup: ConfirmForm confirmForm = modelMapper.map(sessionData, ConfirmForm) expect: confirmForm.name == "田中 太郎" confirmForm.kana == "たなか たろう" confirmForm.sex == SexValues.MALE.text confirmForm.age == "30" confirmForm.job == JobValues.EMPLOYEE.text confirmForm.zipcode == "100-0005" confirmForm.address == "東京都千代田区丸の内" confirmForm.tel == "03-1234-5678" confirmForm.email == "test@sample.co.jp" confirmForm.type1 == Type1Values.PRODUCT.text confirmForm.type2 == [Type2Values.CATALOGUE.text , Type2Values.ESTIMATE.text , Type2Values.OTHER.text].stream() .collect(Collectors.joining("、")) confirmForm.inquiry == "これは\r\nテスト\r\nです。" confirmForm.survey == soh.selectItemList("survey").stream() .map { SurveyOptions surveyOptions -> surveyOptions.itemName } .collect() } def "任意項目には値がセットされていない場合のテスト"() { setup: inquiryInput01Form.job = StringUtils.EMPTY inquiryInput02Form.tel1 = StringUtils.EMPTY inquiryInput02Form.tel2 = StringUtils.EMPTY inquiryInput02Form.tel3 = StringUtils.EMPTY inquiryInput03Form.survey = [] ConfirmForm confirmForm = modelMapper.map(sessionData, ConfirmForm) expect: confirmForm.name == "田中 太郎" confirmForm.kana == "たなか たろう" confirmForm.sex == SexValues.MALE.text confirmForm.age == "30" confirmForm.job == StringUtils.EMPTY confirmForm.zipcode == "100-0005" confirmForm.address == "東京都千代田区丸の内" confirmForm.tel == StringUtils.EMPTY confirmForm.email == "test@sample.co.jp" confirmForm.type1 == Type1Values.PRODUCT.text confirmForm.type2 == [Type2Values.CATALOGUE.text , Type2Values.ESTIMATE.text , Type2Values.OTHER.text].stream() .collect(Collectors.joining("、")) confirmForm.inquiry == "これは\r\nテスト\r\nです。" confirmForm.survey == [] } }
テストを実行して全て成功することを確認します。
SessionData2InquiryDataTypeMap クラスのテストを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/mapper/SessionData2InquiryDataTypeMap.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。
src/test/groovy/ksbysample/webapp/bootnpmgeb/mapper/SessionData2InquiryDataTypeMapTest.groovy が新規作成されるので、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.mapper import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao import ksbysample.webapp.bootnpmgeb.entity.InquiryData import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper import ksbysample.webapp.bootnpmgeb.session.SessionData import ksbysample.webapp.bootnpmgeb.values.JobValues import ksbysample.webapp.bootnpmgeb.values.SexValues import ksbysample.webapp.bootnpmgeb.values.Type1Values import ksbysample.webapp.bootnpmgeb.values.Type2Values import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput02Form import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput03Form import org.apache.commons.lang3.StringUtils import org.modelmapper.ModelMapper import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.util.StreamUtils import spock.lang.Specification import java.nio.charset.StandardCharsets import java.sql.Clob import java.util.stream.Collectors @SpringBootTest class SessionData2InquiryDataTypeMapTest extends Specification { @Autowired private ModelMapper modelMapper @Autowired private SurveyOptionsHelper soh @Autowired private InquiryDataDao inquiryDataDao InquiryInput01Form inquiryInput01Form InquiryInput02Form inquiryInput02Form InquiryInput03Form inquiryInput03Form SessionData sessionData Clob inquiryClob def setup() { inquiryInput01Form = new InquiryInput01Form( lastname: "田中" , firstname: "太郎" , lastkana: "たなか" , firstkana: "たろう" , sex: SexValues.MALE.value , age: "30" , job: JobValues.EMPLOYEE.value ) inquiryInput02Form = new InquiryInput02Form( zipcode1: "100" , zipcode2: "0005" , address: "東京都千代田区丸の内" , tel1: "03" , tel2: "1234" , tel3: "5678" , email: "test@sample.co.jp" ) inquiryInput03Form = new InquiryInput03Form( type1: Type1Values.PRODUCT.value , type2: [Type2Values.CATALOGUE.value , Type2Values.ESTIMATE.value , Type2Values.OTHER.value] , inquiry: "これは\r\nテスト\r\nです。" , survey: soh.selectItemList("survey").stream() .map { SurveyOptions surveyOptions -> surveyOptions.itemValue } .collect() ) sessionData = new SessionData( inquiryInput01Form: inquiryInput01Form , inquiryInput02Form: inquiryInput02Form , inquiryInput03Form: inquiryInput03Form ) inquiryClob = inquiryDataDao.createClob() inquiryClob.setString(1, "これは\r\nテスト\r\nです。") } def "全ての項目に値がセットされている場合のテスト"() { setup: InquiryData inquiryData = modelMapper.map(sessionData, InquiryData) expect: inquiryData.lastname == "田中" inquiryData.firstname == "太郎" inquiryData.lastkana == "たなか" inquiryData.firstkana == "たろう" inquiryData.sex == SexValues.MALE.value inquiryData.age == 30 inquiryData.job == JobValues.EMPLOYEE.value inquiryData.zipcode1 == "100" inquiryData.zipcode2 == "0005" inquiryData.address == "東京都千代田区丸の内" inquiryData.tel1 == "03" inquiryData.tel2 == "1234" inquiryData.tel3 == "5678" inquiryData.email == "test@sample.co.jp" inquiryData.type1 == Type1Values.PRODUCT.value inquiryData.type2 == [Type2Values.CATALOGUE.value , Type2Values.ESTIMATE.value , Type2Values.OTHER.value].stream() .collect(Collectors.joining(",")) StreamUtils.copyToString(inquiryData.inquiry.asciiStream, StandardCharsets.UTF_8) == StreamUtils.copyToString(inquiryClob.asciiStream, StandardCharsets.UTF_8) inquiryData.survey == soh.selectItemList("survey").stream() .map { SurveyOptions surveyOptions -> surveyOptions.itemValue } .collect(Collectors.joining(",")) } def "任意項目には値がセットされていない場合のテスト"() { setup: inquiryInput01Form.job = StringUtils.EMPTY inquiryInput02Form.tel1 = StringUtils.EMPTY inquiryInput02Form.tel2 = StringUtils.EMPTY inquiryInput02Form.tel3 = StringUtils.EMPTY inquiryInput03Form.survey = [] InquiryData inquiryData = modelMapper.map(sessionData, InquiryData) expect: inquiryData.lastname == "田中" inquiryData.firstname == "太郎" inquiryData.lastkana == "たなか" inquiryData.firstkana == "たろう" inquiryData.sex == SexValues.MALE.value inquiryData.age == 30 inquiryData.job == StringUtils.EMPTY inquiryData.zipcode1 == "100" inquiryData.zipcode2 == "0005" inquiryData.address == "東京都千代田区丸の内" inquiryData.tel1 == StringUtils.EMPTY inquiryData.tel2 == StringUtils.EMPTY inquiryData.tel3 == StringUtils.EMPTY inquiryData.email == "test@sample.co.jp" inquiryData.type1 == Type1Values.PRODUCT.value inquiryData.type2 == [Type2Values.CATALOGUE.value , Type2Values.ESTIMATE.value , Type2Values.OTHER.value].stream() .collect(Collectors.joining(",")) StreamUtils.copyToString(inquiryData.inquiry.asciiStream, StandardCharsets.UTF_8) == StreamUtils.copyToString(inquiryClob.asciiStream, StandardCharsets.UTF_8) inquiryData.survey == StringUtils.EMPTY } }
テストを実行して全て成功することを確認します。
InquiryMailHelper クラスのテストを作成する
検証に使用する生成されたメール本文のデータを作成します。src/main/resources/ksbysample/webapp/bootnpmgeb/helper/mail のディレクトリを作成した後、InquiryMailHelperTest-001.txt, InquiryMailHelperTest-002.txt を新規作成し、以下の内容を記述します。
■InquiryMailHelperTest-001.txt
問い合わせフォームから入力された内容は以下の通りです。 お名前(漢字):田中 太郎 お名前(かな):たなか たろう 性別:男性 年齢:30歳 職業:会社員 郵便番号:〒100-0005 住所:東京都千代田区丸の内 電話番号:03-1234-5678 メールアドレス:test@sample.co.jp お問い合わせの種類1:製品に関するお問い合わせ お問い合わせの種類2:資料が欲しい、見積が欲しい、その他の問い合わせ お問い合わせの内容: これは テスト です。 アンケート: ・選択肢1だけ長くしてみる ・選択肢2 ・選択肢3 ・選択肢4 ・選択肢5が少し長い ・選択肢6 ・選択肢7 ・8
■InquiryMailHelperTest-002.txt
問い合わせフォームから入力された内容は以下の通りです。 お名前(漢字):田中 太郎 お名前(かな):たなか たろう 性別:男性 年齢:30歳 職業: 郵便番号:〒100-0005 住所:東京都千代田区丸の内 電話番号: メールアドレス:test@sample.co.jp お問い合わせの種類1:製品に関するお問い合わせ お問い合わせの種類2:資料が欲しい、見積が欲しい、その他の問い合わせ お問い合わせの内容: これは テスト です。 アンケート:
src/main/java/ksbysample/webapp/bootnpmgeb/helper/mail/InquiryMailHelper.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。
src/test/groovy/ksbysample/webapp/bootnpmgeb/helper/mail/InquiryMailHelperTest.groovy が新規作成されるので、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.helper.mail import com.google.common.io.Files import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper import ksbysample.webapp.bootnpmgeb.values.JobValues import ksbysample.webapp.bootnpmgeb.values.SexValues import ksbysample.webapp.bootnpmgeb.values.Type1Values import ksbysample.webapp.bootnpmgeb.values.Type2Values import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm import org.apache.commons.lang3.StringUtils import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.core.io.ClassPathResource import org.springframework.util.StreamUtils import spock.lang.Specification import javax.mail.Message import javax.mail.internet.MimeMessage import java.nio.charset.StandardCharsets import java.util.stream.Collectors @SpringBootTest class InquiryMailHelperTest extends Specification { @Autowired private InquiryMailHelper inquiryMailHelper @Autowired private SurveyOptionsHelper soh ConfirmForm confirmForm def setup() { confirmForm = new ConfirmForm( name: "田中 太郎" , kana: "たなか たろう" , sex: SexValues.MALE.text , age: "30" , job: JobValues.EMPLOYEE.text , zipcode: "100-0005" , address: "東京都千代田区丸の内" , tel: "03-1234-5678" , email: "test@sample.co.jp" , type1: Type1Values.PRODUCT.text , type2: [Type2Values.CATALOGUE.text , Type2Values.ESTIMATE.text , Type2Values.OTHER.text].stream() .collect(Collectors.joining("、")) , inquiry: "これは\r\nテスト\r\nです。" , survey: soh.selectItemList("survey").stream() .map { SurveyOptions surveyOptions -> surveyOptions.itemName } .collect() ) } def "全ての項目に値がセットされている場合のテスト"() { setup: MimeMessage message = inquiryMailHelper.createMessage(confirmForm) expect: message.getFrom()*.toString() == ["inquiry-form@sample.co.jp"] message.getRecipients(Message.RecipientType.TO)*.toString() == ["inquiry@sample.co.jp"] message.getSubject() == "問い合わせフォームからお問い合わせがありました" message.getContent() == Files.asCharSource( new File("src/test/resources/ksbysample/webapp/bootnpmgeb/helper/mail/InquiryMailHelperTest-001.txt") , StandardCharsets.UTF_8).read() } def "任意項目には値がセットされていない場合のテスト"() { setup: confirmForm.job = StringUtils.EMPTY confirmForm.tel = StringUtils.EMPTY confirmForm.survey = [] MimeMessage message = inquiryMailHelper.createMessage(confirmForm) expect: message.getFrom()*.toString() == ["inquiry-form@sample.co.jp"] message.getRecipients(Message.RecipientType.TO)*.toString() == ["inquiry@sample.co.jp"] message.getSubject() == "問い合わせフォームからお問い合わせがありました" message.getContent() == StreamUtils.copyToString( new ClassPathResource("ksbysample/webapp/bootnpmgeb/helper/mail/InquiryMailHelperTest-002.txt") .getInputStream(), StandardCharsets.UTF_8) } }
- Groovy だと
message.getFrom()
のAddress[]
の戻り値をString
の配列かリストに変えたい場合、Arrays.asList(...)
やstream().map()
等を使用しなくても*.toString()
を付ければString[]
になります。
テストを実行して全て成功することを確認します。
履歴
2018/06/23
初版発行。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その60 )( 確認画面を作成する3 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その59 )( 確認画面を作成する2 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 確認画面の作成
- confirm.js を見たら「修正する」ボタンは実装済で変更する必要もなかったので、「送信する」ボタンを実装します。
- 今回はメール送信処理を書きます。
参照したサイト・書籍
目次
手順
メールを送信する処理を実装する
InquiryMailHelper クラスを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/helper/mail の下に InquiryMailHelper.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.helper.mail; import ksbysample.webapp.bootnpmgeb.helper.freemarker.FreeMarkerHelper; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.util.HashMap; import java.util.Map; /** * 問い合わせフォームのメール用 Helper クラス */ @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") @Component public class InquiryMailHelper { private static final String TEMPLATE_LOCATION_TEXTMAIL = "mail/inquirymail-body.ftl"; private static final String FROM_ADDR = "inquiry-form@sample.co.jp"; private static final String TO_ADDR = "inquiry@sample.co.jp"; private static final String SUBJECT = "問い合わせフォームからお問い合わせがありました"; private final FreeMarkerHelper freeMarkerHelper; private final JavaMailSender mailSender; /** * コンストラクタ * * @param freeMarkerHelper {@FreeMarkerHelper} オブジェクト * @param mailSender {@JavaMailSender} オブジェクト */ public InquiryMailHelper(FreeMarkerHelper freeMarkerHelper , JavaMailSender mailSender) { this.freeMarkerHelper = freeMarkerHelper; this.mailSender = mailSender; } /** * {@MimeMessage} オブジェクトを生成する * メール本文は入力画面1~3で入力された内容から生成する * * @param confirmForm {@ConfirmForm} オブジェクト * @return {@MimeMessage} オブジェクト */ public MimeMessage createMessage(ConfirmForm confirmForm) { try { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper message = new MimeMessageHelper(mimeMessage, false, "UTF-8"); message.setFrom(FROM_ADDR); message.setTo(TO_ADDR); message.setSubject(SUBJECT); message.setText(generateTextUsingVelocity(confirmForm), false); return message.getMimeMessage(); } catch (MessagingException e) { throw new RuntimeException(e); } } private String generateTextUsingVelocity(ConfirmForm confirmForm) { Map<String, Object> model = new HashMap<>(); model.put("confirmForm", confirmForm); return freeMarkerHelper.merge(TEMPLATE_LOCATION_TEXTMAIL, model); } }
メールのテンプレートを作成する
src/main/resources/templates の下に mail ディレクトリを新規作成します。
src/main/resources/templates/mail の下に inquirymail-body.ftl を新規作成し、以下の内容を記述します。
問い合わせフォームから入力された内容は以下の通りです。 お名前(漢字):${confirmForm.name!} お名前(かな):${confirmForm.kana!} 性別:${confirmForm.sex!} 年齢:${confirmForm.age!}歳 職業:${confirmForm.job!} 郵便番号:〒${confirmForm.zipcode!} 住所:${confirmForm.address!} 電話番号:${confirmForm.tel!} メールアドレス:${confirmForm.email!} お問い合わせの種類1:${confirmForm.type1!} お問い合わせの種類2:${confirmForm.type2!} お問い合わせの内容: ${confirmForm.inquiry!} アンケート: <#list confirmForm.survey as sv> ・${sv!} </#list>
InquiryConfirmService クラスを変更する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmService.java の以下の点を変更します。
package ksbysample.webapp.bootnpmgeb.web.inquiry; import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao; import ksbysample.webapp.bootnpmgeb.entity.InquiryData; import ksbysample.webapp.bootnpmgeb.helper.mail.EmailHelper; import ksbysample.webapp.bootnpmgeb.helper.mail.InquiryMailHelper; import ksbysample.webapp.bootnpmgeb.session.SessionData; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; import javax.mail.internet.MimeMessage; /** * 確認画面用 Service クラス */ @Service public class InquiryConfirmService { private final InquiryDataDao inquiryDataDao; private final ModelMapper modelMapper; private final InquiryMailHelper inquiryMailHelper; private final EmailHelper emailHelper; /** * コンストラクタ * * @param inquiryDataDao {@InquiryDataDao} オブジェクト * @param modelMapper {@ModelMapper} オブジェクト * @param inquiryMailHelper {@InquiryMailHelper} オブジェクト * @param emailHelper {@EmailHelper} オブジェクト */ public InquiryConfirmService(InquiryDataDao inquiryDataDao , ModelMapper modelMapper , InquiryMailHelper inquiryMailHelper , EmailHelper emailHelper) { this.inquiryDataDao = inquiryDataDao; this.modelMapper = modelMapper; this.inquiryMailHelper = inquiryMailHelper; this.emailHelper = emailHelper; } /** * 入力されたデータを INQUIRY_DATA テーブルに保存してメールを送信する * * @param sessionData {@SessionData} オブジェクト * @param confirmForm {@ConfirmForm} オブジェクト */ public void saveToDbAndSendMail(SessionData sessionData, ConfirmForm confirmForm) { // INQUIRY_DATA テーブルに保存する InquiryData inquiryData = modelMapper.map(sessionData, InquiryData.class); inquiryDataDao.insert(inquiryData); // メールを送信する MimeMessage message = inquiryMailHelper.createMessage(confirmForm); emailHelper.sendMail(message); } }
- フィールドに以下の2行を追加します。コンストラクタにセットする処理も追加します。
private final InquiryMailHelper inquiryMailHelper;
private final EmailHelper emailHelper;
saveToDbAndSendMail
メソッドに以下の2行を追加します。MimeMessage message = inquiryMailHelper.createMessage(confirmForm);
emailHelper.sendMail(message);
動作確認
動作確認します。npm run springboot コマンドを実行し Tomcat を起動した後、ブラウザで http://localhost:9080/inquiry/input/01/ にアクセスします。今回はメールを送信するので smtp4dev も起動します。
入力画面1~3に以下の画像のデータを入力します。
確認画面に以下のように表示されますので、「送信する」ボタンをクリックします。
完了画面が表示されます。
smtp4dev の画面を見るとメールが1通送信されています。「Inspect」ボタンをクリックしてメールの内容を確認します。
ヘッダとメール本文は問題なさそうです。
「職業」「電話番号」「アンケート」を未入力・未選択にしてメールを送信しても、項目が空になるだけでエラーにはなりません。
次は。。。
確認画面のテストを作成します。
履歴
2018/06/17
初版発行。
IntelliJ IDEA を 2018.1.4 → 2018.1.5 へ、Git for Windows を 2.17.0 → 2.17.1(2) へバージョンアップ
IntelliJ IDEA を 2018.1.4 → 2018.1.5 へバージョンアップする
IntelliJ IDEA の 2018.1.5 がリリースされているのでバージョンアップします。
- IntelliJ IDEA 2018.1.5 is released!
https://blog.jetbrains.com/idea/2018/06/intellij-idea-2018-1-5-is-released/
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。
「IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。
Plugin の update も表示されました。このまま「Update and Restart」ボタンをクリックします。
Patch がダウンロードされて IntelliJ IDEA が再起動します。
IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。
IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2018.1.5 へバージョンアップされていることを確認します。
Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。
Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストが全て成功することを確認します。
Git for Windows を 2.17.0 → 2.17.1(2) へバージョンアップする
Git for Windows の 2.17.1(2) がリリースされていたのでバージョンアップします。
https://git-for-windows.github.io/ の「Download」ボタンをクリックして Git-2.17.1.2-64-bit.exe をダウンロードします。
Git-2.17.1.2-64-bit.exe を実行します。
「Git 2.17.1.2 Setup」ダイアログが表示されます。[Next >]ボタンをクリックします。
「Select Components」画面が表示されます。「Git LFS(Large File Support)」だけチェックした状態で [Next >]ボタンをクリックします。
「Choosing the default editor used by Git」画面が表示されます。「Use Vim (the ubiquitous text editor) as Git's default editor」が選択された状態で [Next >]ボタンをクリックします。
「Adjusting your PATH environment」画面が表示されます。中央の「Use Git from the Windows Command Prompt」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Choosing HTTPS transport backend」画面が表示されます。「Use the OpenSSL library」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Configuring the line ending conversions」画面が表示されます。一番上の「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Install]ボタンをクリックします。
インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View Release Notes」のチェックを外した後、「Finish」ボタンをクリックしてインストーラーを終了します。
コマンドプロンプトを起動して
git --version
を実行し、git のバージョンがgit version 2.17.1.windows.2
になっていることを確認します。git-cmd.exe を起動して日本語の表示・入力が問題ないかを確認します。
特に問題はないようですので、2.17.1(2) で作業を進めたいと思います。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その59 )( 確認画面を作成する2 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その58 )( 確認画面を作成する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 確認画面の作成
- confirm.js を見たら「修正する」ボタンは実装済で変更する必要もなかったので、「送信する」ボタンを実装します。
- 2回に分けます。1回目は DB保存処理、2回目はメール送信処理を書きます。
参照したサイト・書籍
Flyway - DOCUMENTATION - SQL-based migrations
https://flywaydb.org/documentation/migrations#sql-based-migrationsファクトリ — Doma 2.0 ドキュメント
http://doma.readthedocs.io/ja/stable/query/factory/
目次
手順
DB に保存する処理を実装する
INQUIRY_DATA テーブルを変更する
INQUIRY_DATA テーブルの以下の点を変更します。
- type2 には入力画面3の「お問い合わせの種類2」で選択された項目の値を "," 区切りで結合した文字列を保存するようにします。また必須項目にしたので
NOT NULL
を追加します。VARCHAR(1)
→VARCHAR(32) NOT NULL
に変更します。 - survey には入力画面3の「アンケート」で選択された項目の値を "," 区切りで結合した文字列を保存するようにします。また任意項目にしたので
NOT NULL
を削除します。VARCHAR(1) NOT NULL
→VARCHAR(32)
に変更します。
今回は V1__init.sql
はそのままで、V1.1__Alter_column.sql
を追加してみます。src/main/resources/db/migration の下に V1.1__Alter_column.sql
を新規作成し、以下の内容を記述します。
ALTER TABLE INQUIRY_DATA ALTER COLUMN type2 VARCHAR(32) NOT NULL; ALTER TABLE INQUIRY_DATA ALTER COLUMN survey VARCHAR(32);
動作確認します。Tomcat を起動した後、IntelliJ IDEA の Database Tools で「Synchronize」ボタンを押して更新し、カラムの定義が変更されていることを確認します。
ModelMapper で使用する SessionData → InquiryData データ変換用クラスを作成する
まず今回は INQUIRY_DATA テーブルの Entity クラス ksbysample.webapp.bootnpmgeb.entity.InquiryData に Clob
型を使用しているので、
@Entity @Table(name = "INQUIRY_DATA") public class InquiryData { .......... @Column(name = "INQUIRY") Clob inquiry; .......... }
CLOB を使用するためのメソッドを Dao インターフェースに追加します。src/main/java/ksbysample/webapp/bootnpmgeb/dao/InquiryDataDao.java の以下の点を変更します。
@Dao @ComponentAndAutowiredDomaConfig public interface InquiryDataDao { .......... /** * Clob 生成用 * @return {@Clob} オブジェクト */ @ClobFactory Clob createClob(); }
@ClobFactory Clob createClob();
を追加します。
次に src/main/java/ksbysample/webapp/bootnpmgeb/mapper の下に SessionData2InquiryDataTypeMap.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.mapper; import com.github.rozidan.springboot.modelmapper.TypeMapConfigurer; import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao; import ksbysample.webapp.bootnpmgeb.entity.InquiryData; import ksbysample.webapp.bootnpmgeb.session.SessionData; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput03Form; import org.modelmapper.TypeMap; import org.springframework.stereotype.Component; import java.sql.Clob; import java.sql.SQLException; import java.time.LocalDateTime; import java.util.stream.Collectors; /** * SessionData --> InquiryData データ変換用クラス */ @SuppressWarnings({"checkstyle:LineLength", "PMD.AvoidThrowingRawExceptionTypes"}) @Component public class SessionData2InquiryDataTypeMap extends TypeMapConfigurer<SessionData, InquiryData> { private final InquiryDataDao inquiryDataDao; /** * コンストラクタ * * @param inquiryDataDao {@InquiryDataDao} オブジェクト */ public SessionData2InquiryDataTypeMap(InquiryDataDao inquiryDataDao) { super(); this.inquiryDataDao = inquiryDataDao; } @Override public void configure(TypeMap<SessionData, InquiryData> typeMap) { typeMap.setPreConverter(context -> { SessionData sessionData = context.getSource(); InquiryInput03Form inquiryInput03Form = sessionData.getInquiryInput03Form(); InquiryData inquiryData = context.getDestination(); inquiryData.setType2(inquiryInput03Form.getType2().stream().collect(Collectors.joining(","))); Clob inquiryClob = inquiryDataDao.createClob(); try { inquiryClob.setString(1, inquiryInput03Form.getInquiry()); } catch (SQLException e) { throw new RuntimeException(e); } inquiryData.setInquiry(inquiryClob); inquiryData.setSurvey(inquiryInput03Form.getSurvey().stream().collect(Collectors.joining(","))); inquiryData.setUpdateDate(LocalDateTime.now()); return context.getDestination(); }); typeMap.addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getLastname(), InquiryData::setLastname)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getFirstname(), InquiryData::setFirstname)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getLastkana(), InquiryData::setLastkana)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getFirstkana(), InquiryData::setFirstkana)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getSex(), InquiryData::setSex)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getAge(), InquiryData::setAge)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getJob(), InquiryData::setJob)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getZipcode1(), InquiryData::setZipcode1)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getZipcode2(), InquiryData::setZipcode2)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getAddress(), InquiryData::setAddress)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getTel1(), InquiryData::setTel1)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getTel2(), InquiryData::setTel2)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getTel3(), InquiryData::setTel3)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getEmail(), InquiryData::setEmail)) .addMappings(mapping -> mapping.map(src -> src.getInquiryInput03Form().getType1(), InquiryData::setType1)) .addMappings(mapping -> mapping.skip(InquiryData::setType2)) .addMappings(mapping -> mapping.skip(InquiryData::setInquiry)) .addMappings(mapping -> mapping.skip(InquiryData::setSurvey)) .addMappings(mapping -> mapping.skip(InquiryData::setUpdateDate)); } }
.addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getFirstname(), InquiryData::set~))
の行が 120文字を超えているのですが、改行したくないので@SuppressWarnings
に"checkstyle:LineLength"
を付加しています。Clob::setString
メソッドはSQLException
を throw する可能性があるので try-catch 文を記述していますが、throw されたSQLException
はRuntimeException
で throw することにします。このままだと PMD で警告が出るので@SuppressWarnings
に"PMD.AvoidThrowingRawExceptionTypes"
を付加しています(実際には適切な例外クラスを定義して使用しましょう)。typeMap.setPreConverter( ... )
内には個別の変換処理が必要なものだけ記述し、単に Getter メソッドで取得して Setter メソッドでセットすればよいものは.addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getFirstname(), InquiryData::set~))
で記述します。
今の config/checkstyle/google_checks.xml の設定では @SuppressWarnings
が反映されないので、以下の点を変更します。
<module name="Checker"> .......... <module name="SuppressWarningsFilter"/> <module name="TreeWalker"> <module name="SuppressWarningsHolder"/> <module name="OuterTypeFilename"/> ..........
- 以下の2行を追加します。
<module name="SuppressWarningsFilter"/>
<module name="SuppressWarningsHolder"/>
InquiryConfirmService クラスを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryConfirmService.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.web.inquiry; import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao; import ksbysample.webapp.bootnpmgeb.entity.InquiryData; import ksbysample.webapp.bootnpmgeb.session.SessionData; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; /** * 確認画面用 Service クラス */ @Service public class InquiryConfirmService { private final InquiryDataDao inquiryDataDao; private final ModelMapper modelMapper; /** * コンストラクタ * * @param inquiryDataDao {@InquiryDataDao} オブジェクト * @param modelMapper {@ModelMapper} オブジェクト */ public InquiryConfirmService(InquiryDataDao inquiryDataDao , ModelMapper modelMapper) { this.inquiryDataDao = inquiryDataDao; this.modelMapper = modelMapper; } /** * 入力されたデータを INQUIRY_DATA テーブルに保存してメールを送信する * * @param sessionData {@SessionData} オブジェクト * @param confirmForm {@ConfirmForm} オブジェクト */ public void saveToDbAndSendMail(SessionData sessionData, ConfirmForm confirmForm) { // INQUIRY_DATA テーブルに保存する InquiryData inquiryData = modelMapper.map(sessionData, InquiryData.class); inquiryDataDao.insert(inquiryData); // メールを送信する } }
InquiryConfirmController クラスを変更する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmController.java の以下の点を変更します。
@Controller @RequestMapping("/inquiry/confirm") @SessionAttributes("sessionData") public class InquiryConfirmController { .......... private final ModelMapper modelMapper; private final InquiryConfirmService inquiryConfirmService; /** * コンストラクタ * * @param modelMapper {@ModelMapper} オブジェクト * @param inquiryConfirmService {@InquiryConfirmService} オブジェクト */ public InquiryConfirmController(ModelMapper modelMapper , InquiryConfirmService inquiryConfirmService) { this.modelMapper = modelMapper; this.inquiryConfirmService = inquiryConfirmService; } .......... /** * 確認画面 「送信する」ボタンクリック時の処理 * * @param sessionData {@SessionData} オブジェクト * @param builder {@UriComponentsBuilder} オブジェクト * @return 完了画面の URL */ @PostMapping("/send") public String send(SessionData sessionData , UriComponentsBuilder builder) { ConfirmForm confirmForm = modelMapper.map(sessionData, ConfirmForm.class); inquiryConfirmService.saveToDbAndSendMail(sessionData, confirmForm); return UrlBasedViewResolver.REDIRECT_URL_PREFIX + builder.path(UrlConst.URL_INQUIRY_COMPLETE).toUriString(); } }
private final InquiryConfirmService inquiryConfirmService;
を追加します。合わせてコンストラクタにセットする処理を追加します。send
メソッドの引数にSessionData sessionData
を追加します。send
メソッド内に以下の2行を追加します。confirmForm
はメールの送信処理で使用します。ConfirmForm confirmForm = modelMapper.map(sessionData, ConfirmForm.class);
inquiryConfirmService.saveToDbAndSendMail(sessionData, confirmForm);
動作確認
動作確認します。npm run springboot コマンドを実行し Tomcat を起動した後、ブラウザで http://localhost:9080/inquiry/input/01/ にアクセスします。
入力画面1~3に以下の画像のデータを入力します。
確認画面に以下のように表示されますので、「送信する」ボタンをクリックします。
完了画面が表示されます。
IntelliJ IDEA の Database Tools から INQUIRY_DATA テーブルのデータを確認すると、問題なく保存されていることが確認できます。
履歴
2018/06/17
初版発行。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その58 )( 確認画面を作成する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- 確認画面の作成
- 今回は入力されたデータを画面に表示する処理を作成します。保存された入力値を画面に表示する文字列に変換する処理を ModelMapper のデータ変換用クラス内で行います。
参照したサイト・書籍
目次
- Form クラスを作成する
- ModelMapper で使用するデータ変換用クラスを作成する
- InquiryConfirmController クラスを変更する
- confirm.html を変更する
- 動作確認
- 次は。。。
手順
Form クラスを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form の下に ConfirmForm.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.web.inquiry.form; import lombok.Data; /** * 確認画面用 Form クラス */ @Data public class ConfirmForm { /************************ * 入力画面1の入力項目用 ************************/ private String name; private String kana; private String sex; private String age; private String job; /************************ * 入力画面2の入力項目用 ************************/ private String zipcode; private String address; private String tel; private String email; /************************ * 入力画面3の入力項目用 ************************/ private String type1; private String type2; private String inquiry; private List<String> survey; }
ModelMapper で使用するデータ変換用クラスを作成する
データ変換のルールを定義するクラスを配置するパッケージを作成します。src/main/java/ksbysample/webapp/bootnpmgeb の下に mapper パッケージを作成します。
src/main/java/ksbysample/webapp/bootnpmgeb/mapper の下に SessionData2ConfirmFormTypeMap.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.mapper; import com.github.rozidan.springboot.modelmapper.TypeMapConfigurer; import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper; import ksbysample.webapp.bootnpmgeb.session.SessionData; import ksbysample.webapp.bootnpmgeb.values.*; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput02Form; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput03Form; import org.apache.commons.lang3.StringUtils; import org.modelmapper.TypeMap; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; /** * SessionData --> ConfirmForm データ変換用クラス */ @Component public class SessionData2ConfirmFormTypeMap extends TypeMapConfigurer<SessionData, ConfirmForm> { private final ValuesHelper vh; private final SurveyOptionsHelper soh; /** * コンストラクタ * * @param vh {@ValuesHelper} オブジェクト * @param soh {@SurveyOptionsHelper} オブジェクト */ public SessionData2ConfirmFormTypeMap(ValuesHelper vh , SurveyOptionsHelper soh) { super(); this.vh = vh; this.soh = soh; } @Override public void configure(TypeMap<SessionData, ConfirmForm> typeMap) { typeMap.setPreConverter(context -> { SessionData sessionData = context.getSource(); InquiryInput01Form inquiryInput01Form = sessionData.getInquiryInput01Form(); InquiryInput02Form inquiryInput02Form = sessionData.getInquiryInput02Form(); InquiryInput03Form inquiryInput03Form = sessionData.getInquiryInput03Form(); ConfirmForm confirmForm = context.getDestination(); /************************ * 入力画面1の入力項目用 ************************/ confirmForm.setName(join(" " , inquiryInput01Form.getLastname() , inquiryInput01Form.getFirstname())); confirmForm.setKana(join(" " , inquiryInput01Form.getLastkana() , inquiryInput01Form.getFirstkana())); confirmForm.setSex(vh.getText(SexValues.class, inquiryInput01Form.getSex())); confirmForm.setAge(inquiryInput01Form.getAge()); confirmForm.setJob(vh.getText(JobValues.class, inquiryInput01Form.getJob())); /************************ * 入力画面2の入力項目用 ************************/ confirmForm.setZipcode(join("-" , inquiryInput02Form.getZipcode1() , inquiryInput02Form.getZipcode2())); confirmForm.setAddress(inquiryInput02Form.getAddress()); confirmForm.setTel(join("-" , inquiryInput02Form.getTel1() , inquiryInput02Form.getTel2() , inquiryInput02Form.getTel3())); confirmForm.setEmail(inquiryInput02Form.getEmail()); /************************ * 入力画面3の入力項目用 ************************/ confirmForm.setType1(vh.getText(Type1Values.class, inquiryInput03Form.getType1())); confirmForm.setType2(inquiryInput03Form.getType2().stream() .map(type2 -> vh.getText(Type2Values.class, type2)) .collect(Collectors.joining("、"))); confirmForm.setInquiry(inquiryInput03Form.getInquiry()); Map<String, String> surveyOptionsMap = this.soh.selectItemList("survey").stream() .collect(Collectors.toMap(s -> s.getItemValue(), s -> s.getItemName())); confirmForm.setSurvey(inquiryInput03Form.getSurvey().stream() .map(survey -> surveyOptionsMap.get(survey)) .collect(Collectors.toList())); return context.getDestination(); }); typeMap.addMappings(mapping -> mapping.skip(ConfirmForm::setName)) .addMappings(mapping -> mapping.skip(ConfirmForm::setKana)) .addMappings(mapping -> mapping.skip(ConfirmForm::setSex)) .addMappings(mapping -> mapping.skip(ConfirmForm::setAge)) .addMappings(mapping -> mapping.skip(ConfirmForm::setJob)) .addMappings(mapping -> mapping.skip(ConfirmForm::setZipcode)) .addMappings(mapping -> mapping.skip(ConfirmForm::setAddress)) .addMappings(mapping -> mapping.skip(ConfirmForm::setTel)) .addMappings(mapping -> mapping.skip(ConfirmForm::setEmail)) .addMappings(mapping -> mapping.skip(ConfirmForm::setType1)) .addMappings(mapping -> mapping.skip(ConfirmForm::setType2)) .addMappings(mapping -> mapping.skip(ConfirmForm::setInquiry)) .addMappings(mapping -> mapping.skip(ConfirmForm::setSurvey)); } /** * 文字列を指定された区切り文字で結合する。文字列が全て空の場合には空文字列を返す。 * * @param delimiter 区切り文字 * @param arg 結合する文字列の配列 * @return 結合した文字列 */ private String join(String delimiter, String... arg) { boolean isAllEmpty = arg == null || Arrays.asList(arg).stream().allMatch(str -> StringUtils.isEmpty(str)); return isAllEmpty ? StringUtils.EMPTY : Arrays.asList(arg).stream().collect(Collectors.joining(delimiter)); } }
InquiryConfirmController クラスを変更する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmController.java の以下の点を変更します。
@Controller @RequestMapping("/inquiry/confirm") @SessionAttributes("sessionData") public class InquiryConfirmController { private static final String TEMPLATE_BASE = "web/inquiry"; private static final String TEMPLATE_CONFIRM = TEMPLATE_BASE + "/confirm"; private final ModelMapper modelMapper; /** * コンストラクタ * * @param modelMapper {@ModelMapper} オブジェクト */ public InquiryConfirmController(ModelMapper modelMapper) { this.modelMapper = modelMapper; } /** * 確認画面 初期表示処理 * * @param confirmForm {@ConfirmForm} オブジェクト * @param sessionData {@SessionData} オブジェクト * @return 確認画面の Thymeleaf テンプレートファイルのパス */ @GetMapping public String index(ConfirmForm confirmForm , SessionData sessionData) { modelMapper.map(sessionData, confirmForm); return TEMPLATE_CONFIRM; } .......... }
private final ModelMapper modelMapper;
を追加します。- コンストラクタを追加します。
- index メソッドに
ConfirmForm confirmForm
,SessionData sessionData
の引数を追加し、modelMapper.map(sessionData, confirmForm);
を追加します。
confirm.html を変更する
src/main/resources/templates/web/inquiry/confirm.html の以下の点を変更します。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}"> <title>入力フォーム - 確認画面</title> <style> /* セルの上部の罫線を表示しないようようにし、セル内の余白を詰める */ .table > tbody > tr > th, .table > tbody > tr > td { border-top: none; padding: 5px; } </style> </head> <body class="skin-blue layout-top-nav"> <div class="wrapper"> <!-- Content Wrapper. Contains page content --> <div class="content-wrapper"> <!-- Content Header (Page header) --> <section class="content-header"> <h1> 確認画面 </h1> </section> <!-- Main content --> <section class="content"> <div class="row"> <div class="col-xs-12"> <!--/*@thymesVar id="confirmForm" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm"*/--> <form id="confirmForm" method="post" action="" th:action="@{/inquiry/confirm/}" th:object="${confirmForm}"> <table class="table"> <colgroup> <col width="15%"/> <col width="85%"/> </colgroup> <!-- 入力画面1の項目 --> <tr> <th nowrap>お名前(漢字)</th> <td th:text="*{name}">田中 太郎</td> </tr> <tr> <th nowrap>お名前(かな)</th> <td th:text="*{kana}">たなか たろう</td> </tr> <tr> <th nowrap>性別</th> <td th:text="*{sex}">男性</td> </tr> <tr> <th nowrap>年齢</th> <td> <th:block th:text="*{age}">30</th:block> 歳 </td> </tr> <tr> <th nowrap>職業</th> <td th:text="*{job}">会社員</td> </tr> <tr> <td colspan="2"> <button class="btn bg-blue js-btn-input01"><i class="fa fa-arrow-left"></i> 修正する</button> </td> </tr> <!-- 入力画面2の項目 --> <tr> <th nowrap>郵便番号</th> <td>〒 <th:block th:text="*{zipcode}">102-0072</th:block> </td> </tr> <tr> <th nowrap>住所</th> <td th:text="*{address}">東京都千代田区飯田橋1-1</td> </tr> <tr> <th nowrap>電話番号</th> <td th:text="*{tel}">03-1234-5678</td> </tr> <tr> <th nowrap>メールアドレス</th> <td th:text="*{email}">taro.tanaka@sample.co.jp</td> </tr> <tr> <td colspan="2"> <button class="btn bg-blue js-btn-input02"><i class="fa fa-arrow-left"></i> 修正する</button> </td> </tr> <!-- 入力画面3の項目 --> <tr> <th nowrap>お問い合わせの種類1</th> <td th:text="*{type1}">製品に関するお問い合わせ</td> </tr> <tr> <th nowrap>お問い合わせの種類2</th> <td th:text="*{type2}">見積が欲しい</td> </tr> <tr> <th nowrap>お問い合わせの内容</th> <td th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), T(java.lang.System).getProperty('line.separator'), '<br />')} : ''"> ここに、<br/> 入力されたお問い合わせの内容が表示されます。 </td> </tr> <tr> <th nowrap>アンケート</th> <td> <ul style="padding-left: 20px" th:each="sv : *{survey}"> <li th:text="${sv}">選択肢1だけ長くしてみる</li> </ul> </td> </tr> <tr> <td colspan="2"> <button class="btn bg-blue js-btn-input03"><i class="fa fa-arrow-left"></i> 修正する</button> </td> </tr> </table> <div class="text-center"> <button class="btn bg-green js-btn-send"><i class="fa fa-arrow-right"></i> 送信する</button> </div> </form> </div> </div> </section> <!-- /.content --> </div> <!-- /.content-wrapper --> </div> <!-- ./wrapper --> <!-- REQUIRED JS SCRIPTS --> <script src="/js/inquiry/confirm.js"></script> </body> </html>
- form タグの上に
<!--/*@thymesVar id="confirmForm" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm"*/-->
を追加します。 - form タグの末尾に
th:object="${confirmForm}"
を追加します。 - 各表示項目を以下のように変更します。
- 基本的には td タグに
th:text="*{...}"
(... には入力項目に対応した変数を記述) を追加します。 - textarea の入力項目である「お問い合わせの内容」には
th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), T(java.lang.System).getProperty('line.separator'), '<br />')} : ''"
を追加します。改行は<br/>
に変換して出力します。 - 「アンケート」はリストを出力するので ul タグに
th:each="sv : *{survey}"
を、li タグにth:text="${sv}"
を追加します。
- 基本的には td タグに
動作確認
動作確認します。npm run springboot コマンドを実行し Tomcat を起動した後、ブラウザで http://localhost:9080/inquiry/input/01/ にアクセスします。
入力画面1~3に以下の画像のデータを入力します。
確認画面を表示すると入力したデータが表示されます。
- ラジオボタンやドロップダウンリストで選択する項目は、選択された文字列が表示されています。
- 「郵便番号」や「電話番号」は入力した値が "-" で結合して表示されています。
- 「お問い合わせの種類2」は選択した項目が "、" で結合して表示されています。
- 「お問い合わせの内容」は改行した箇所は改行されて表示されています。
- 「アンケート」は選択した項目が列挙されています。
「職業」や「アンケート」を選択しなかったり、「電話番号」を入力しないと、
確認画面の「職業」「電話番号」「アンケート」には何も表示されません。
次は。。。
「修正する」ボタンの処理→「送信する」ボタンの処理の順に実装します。
履歴
2018/06/13
初版発行。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その57 )( build.gradle に記述する BOM を Spring IO Platform のものから Spring Boot のものに変更する )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その56 )( PMD を 5.8.1 → 6.4.0 へバージョンアップする2 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- build.gradle に記述する BOM は Spring IO Platform のものを利用していたのですが、9 April 2019 に EOL となることが決まったので Spring Boot の BOM を利用するように変更します。
参照したサイト・書籍
Spring IO platform
http://platform.spring.io/platform/67. Spring Boot Gradle plugin
https://docs.spring.io/autorepo/docs/spring-boot/1.5.10.RELEASE/reference/html/build-tool-plugins-gradle-plugin.htmlSpring Boot Gradle Plugin Reference Guide
https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/gradle-plugin/reference/html/- Spring Boot 2 系の Spring Boot Gradle Plugin のマニュアルです。参考にメモしておきます。
spring-boot-starter-parent
https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/spring-boot-dependencies
https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/
目次
手順
build.gradle を変更する
67. Spring Boot Gradle plugin に記載されているように Spring Boot Gradle plugin を使用していれば spring-boot-starter-parent の BOM が自動で適用されるのですが、個人的な好みで BOM は明記することにします。
build.gradle の以下の点を変更します。
dependencyManagement { imports { // mavenBom は以下の URL のものを使用する // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/1.5.10.RELEASE/ // bomProperty に指定可能な property は以下の URL の BOM に記述がある // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/1.5.10.RELEASE/spring-boot-dependencies-1.5.10.RELEASE.pom mavenBom("org.springframework.boot:spring-boot-starter-parent:${springBootVersion}") { bomProperty 'thymeleaf.version', '3.0.9.RELEASE' bomProperty 'thymeleaf-extras-springsecurity4.version', '3.0.2.RELEASE' bomProperty 'thymeleaf-layout-dialect.version', '2.2.2' bomProperty 'thymeleaf-extras-data-attribute.version', '2.0.1' bomProperty 'thymeleaf-extras-java8time.version', '3.0.1.RELEASE' } } } dependencies { .......... // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの .......... compile("com.google.guava:guava:22.0") compile("org.apache.commons:commons-lang3:3.7") .......... }
mavenBom("io.spring.platform:platform-bom:Brussels-SR7")
→mavenBom("org.springframework.boot:spring-boot-starter-parent:${springBootVersion}")
に変更します。bomProperty 'guava.version', '22.0'
を削除します。- Spring Boot の BOM では guava と commons-lang3 はサポートされていないので、記述する位置を変更し、バージョンを明記します。
compile("com.google.guava:guava")
→compile("com.google.guava:guava:22.0")
compile("org.apache.commons:commons-lang3")
→compile("org.apache.commons:commons-lang3:3.7")
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
モジュールのバージョンを確認します。Spring Boot は 1.5.10.RELEASE が使用されており、
Thymeleaf も 3.0.9.RELEASE が使用されています。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行します。1件だけある必ず失敗するテストをコメントアウトせずに build タスクを実行したので test タスクはエラーが出ましたが、それ以外は問題なさそうです。また BOM を変更したことで追加でダウンロードされたモジュールがありました。
guava-23.5-jre.pom
がダウンロードされていたので、プロジェクトで使用する guava も build.gradle で指定している 22.0 でないのか?と思い gradlew dependencies
コマンドを実行して確認したところ、23 を使用しているのは checkstyle で、Web アプリの方では 22.0 が使用されていました。
checkstyle - The Checkstyle libraries to be used for this project. \--- com.puppycrawl.tools:checkstyle:8.8 +--- antlr:antlr:2.7.7 +--- org.antlr:antlr4-runtime:4.7.1 +--- commons-beanutils:commons-beanutils:1.9.3 | \--- commons-collections:commons-collections:3.2.2 +--- commons-cli:commons-cli:1.4 +--- com.google.guava:guava:23.6-jre | +--- com.google.code.findbugs:jsr305:1.3.9 | +--- org.checkerframework:checker-compat-qual:2.0.0 | +--- com.google.errorprone:error_prone_annotations:2.1.3 | +--- com.google.j2objc:j2objc-annotations:1.1 | \--- org.codehaus.mojo:animal-sniffer-annotations:1.14 \--- net.sf.saxon:Saxon-HE:9.8.0-7 compile - Dependencies for source set 'main' (deprecated, use 'implementation ' instead). +--- org.springframework.boot:spring-boot-starter-web -> 1.5.10.RELEASE .......... +--- com.google.guava:guava:22.0 | +--- com.google.code.findbugs:jsr305:1.3.9 | +--- com.google.errorprone:error_prone_annotations:2.0.18 | +--- com.google.j2objc:j2objc-annotations:1.1 | \--- org.codehaus.mojo:animal-sniffer-annotations:1.14 +--- org.apache.commons:commons-lang3:3.7 \--- org.seasar.doma:doma:2.19.1
build.gradle の plugin の記述を plugins { id '...' }
の書式に変更する
今回の変更で 67. Spring Boot Gradle plugin を見て、今の build.gradle の plugin の書き方が古いことに気づいたので、新しい書き方に変更します。
build.gradle の以下の点を変更します。
buildscript { ext { group "ksbysample" version "1.0.1-RELEASE" springBootVersion = "1.5.10.RELEASE" } } plugins { id "java" id "eclipse" id "idea" // plugins {} block 内では ${springBootVersion} が使用できないので、バージョンを直接記述している id "org.springframework.boot" version "1.5.10.RELEASE" id "groovy" id "net.ltgt.errorprone" version "0.0.13" id "checkstyle" id "findbugs" id "pmd" id "com.moowork.node" version "1.2.0" } sourceCompatibility = 1.8 targetCompatibility = 1.8 ..........
buildscript { ... }
の前にgroup ...
、version ...
を記述しているとonly buildscript {} and other plugins {} script blocks are allowed before plugins {} blocks, no other statements are allowed
というエラーメッセージが出力されますので、以下の2行をbuildscript { ext { ... } }
の中に移動します。group 'ksbysample'
version '1.0.0-RELEASE'
buildscript { ... }
内のrepositories { ... }
、dependencies { ... }
を削除します。apply plugin: '...'
の記述全てをplugins { ... }
で囲み、apply plugin:
→id
に変更します。- 以下の3つのプラグインは
version ...
を追加します。id "org.springframework.boot"
id "net.ltgt.errorprone"
id "com.moowork.node"
id "org.springframework.boot"
では version ${springBootVersion}
と記述したかったのですが、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新するとエラーが出ます。Allow the plugin DSL to expand properties as part of the version によるとまだ変数は使用できないようです。今は直接記述するようにします。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行して BUILD SUCCESSFUL が出力されることを確認します(失敗するテストはコメントアウトしてあります)。
履歴
2018/06/10
初版発行。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その56 )( PMD を 5.8.1 → 6.4.0 へバージョンアップする2 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その55 )( PMD を 5.8.1 → 6.4.0 へバージョンアップする ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- PMD を 5.8.1 → 6.4.0 へバージョンアップします。前回からの続きです。
参照したサイト・書籍
目次
- category/java/documentation.xml
- category/java/errorprone.xml
- category/java/multithreading.xml
- category/java/performance.xml
- category/java/security.xml
- config/pmd/pmd-project-rulesets.xml はこうなりました
手順
category/java/documentation.xml
設定を追加して build タスクを実行すると 112 PMD rule violations were found.
と出力されました。メッセージは以下の4種類でした。
Field comments are required
Document empty method body
Comment is too large: Line too long
Enum comments are required
Field comments are required
CommentRequired のルール違反のメッセージです。前は <property name="fieldCommentRequirement" value="Ignored"/>
と設定していたので、今回も同じ設定を入れます。
Document empty method body
UncommentedEmptyMethodBody のルール違反のメッセージです。前も exclude していたので、今回も exclude します。
Comment is too large: Line too long
CommentSize のルール違反のメッセージです。
このルールでは1行の最大文字数が 80 文字に設定されていますが IntelliJ IDEA では1行の最大文字数は default では 120 なので、<property name="maxLineLength" value="120"/>
を入れて IntelliJ IDEA と同じ文字数になるようにします。
Enum comments are required
これも CommentRequired のルール違反のメッセージです。前は <property name="enumCommentRequirement" value="Ignored"/>
と設定していたので、今回も同じ設定を入れます。
以上で category/java/documentation.xml は以下のような定義になりました。
<rule ref="category/java/documentation.xml"> <!-- CommentRequired はここでは exclude し、下で別途定義する --> <exclude name="CommentRequired"/> <exclude name="UncommentedEmptyMethodBody"/> <!-- CommentSize はここでは exclude し、下で別途定義する --> <exclude name="CommentSize"/> </rule> <rule ref="category/java/documentation.xml/CommentRequired"> <properties> <property name="fieldCommentRequirement" value="Ignored"/> <property name="enumCommentRequirement" value="Ignored"/> </properties> </rule> <rule ref="category/java/documentation.xml/CommentSize"> <properties> <property name="maxLineLength" value="120"/> </properties> </rule>
category/java/errorprone.xml
設定を追加して build タスクを実行すると 30 PMD rule violations were found.
と出力されました。メッセージは以下の4種類でした。
Found '...'-anomaly for variable '...' (lines ...).
Found non-transient, non-static member. Please mark as transient or provide accessors.
Avoid using Literals in Conditional Statements
Class cannot be instantiated and does not provide any static methods or fields
Found '...'-anomaly for variable '...' (lines ...).
DataflowAnomalyAnalysis のルール違反のメッセージです。
メッセージが出た箇所を見ると lambda 式の引数で出ているようで、対応のしようがないので exclude します。lambda 式でルール違反のメッセージが出るようでは、このルールは使えないのでは?
Found non-transient, non-static member. Please mark as transient or provide accessors.
BeanMembersShouldSerialize のルール違反のメッセージです。
メッセージが出た箇所を見ると DI 用のフィールド変数でした。対応のしようがないので exclude します。
Avoid using Literals in Conditional Statements
AvoidLiteralsInIfCondition のルール違反のメッセージです。
条件式で定数を直接記述していると出るメッセージでした。定数文字列を定義してそれを使用するようソースを修正します。
Class cannot be instantiated and does not provide any static methods or fields
MissingStaticMethodInNonInstantiatableClass のルール違反のメッセージです。
リンク先の説明を読みましたが、今は必要そうに思えなかったので exclude することにします。
以上で category/java/errorprone.xml は以下のような定義になりました。
<rule ref="category/java/errorprone.xml"> <exclude name="DataflowAnomalyAnalysis"/> <exclude name="BeanMembersShouldSerialize"/> <exclude name="MissingStaticMethodInNonInstantiatableClass"/> </rule>
category/java/multithreading.xml
設定を追加して build タスクを実行しても PMD のルール違反は出ませんでした。
category/java/performance.xml
設定を追加して build タスクを実行すると 11 PMD rule violations were found.
と出力されました。メッセージは以下の2種類でした。
StringBuffer (or StringBuilder).append is called consecutively without reusing the target variable.
Avoid using redundant field initializer for '...'
StringBuffer (or StringBuilder).append is called consecutively without reusing the target variable.
ConsecutiveAppendsShouldReuse のルール違反のメッセージです。
指摘された箇所を
sb.append("ControllerName = "); sb.append(loggingGamenName); sb.append(", ");
以下のように修正します。
sb.append("ControllerName = ") .append(loggingGamenName) .append(", ");
Avoid using redundant field initializer for '...'
RedundantFieldInitializer のルール違反のメッセージです。
リンク先を読むとフィールド変数をデフォルト値と同じ値で変数を初期化しているとメッセージが表示されるルールでした。確かにメッセージが出ている箇所を確認したところ boolean 型のフィールド変数を false で初期化していました。
メッセージが出ているフィールド変数に false をセットしている処理を削除することにします。
category/java/security.xml
設定を追加して build タスクを実行しても PMD のルール違反は出ませんでした。
config/pmd/pmd-project-rulesets.xml はこうなりました
最終版の config/pmd/pmd-project-rulesets.xml を記載しておきます。最初はバージョンアップ後に大量にメッセージが出力されたので移行は大変かと思いましたが、Rule が8種類のCategory に分類されたことと、独自 Rulesets の書き方さえ分かれば大したことはありませんでした。
<?xml version="1.0" encoding="UTF-8"?> <ruleset name="mybraces" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> <description>project rulesets</description> <!-- rulesets の種類・説明は 以下の URL 参照 https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/category/java https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/rulesets/java https://pmd.github.io/pmd-6.4.0/pmd_rules_java.html ※"pmd-6.4.0" の部分は適用しているバージョンに変更すること。 --> <rule ref="category/java/bestpractices.xml"/> <rule ref="category/java/codestyle.xml"> <exclude name="VariableNamingConventions"/> <exclude name="MethodArgumentCouldBeFinal"/> <exclude name="LongVariable"/> <exclude name="LocalVariableCouldBeFinal"/> <exclude name="AtLeastOneConstructor"/> <exclude name="ShortVariable"/> <exclude name="UnnecessaryAnnotationValueElement"/> <exclude name="ClassNamingConventions"/> <exclude name="DefaultPackage"/> <exclude name="CommentDefaultAccessModifier"/> <exclude name="OnlyOneReturn"/> </rule> <rule ref="category/java/design.xml"> <exclude name="LoosePackageCoupling"/> <exclude name="UseUtilityClass"/> <exclude name="NcssCount"/> <exclude name="LawOfDemeter"/> <exclude name="DataClass"/> <exclude name="UseObjectForClearerAPI"/> <exclude name="CyclomaticComplexity"/> </rule> <rule ref="category/java/documentation.xml"> <!-- CommentRequired はここでは exclude し、下で別途定義する --> <exclude name="CommentRequired"/> <exclude name="UncommentedEmptyMethodBody"/> <!-- CommentSize はここでは exclude し、下で別途定義する --> <exclude name="CommentSize"/> </rule> <rule ref="category/java/documentation.xml/CommentRequired"> <properties> <property name="fieldCommentRequirement" value="Ignored"/> <property name="enumCommentRequirement" value="Ignored"/> </properties> </rule> <rule ref="category/java/documentation.xml/CommentSize"> <properties> <property name="maxLineLength" value="120"/> </properties> </rule> <rule ref="category/java/errorprone.xml"> <exclude name="DataflowAnomalyAnalysis"/> <exclude name="BeanMembersShouldSerialize"/> <exclude name="MissingStaticMethodInNonInstantiatableClass"/> </rule> <rule ref="category/java/multithreading.xml"/> <rule ref="category/java/performance.xml"/> <rule ref="category/java/security.xml"/> </ruleset>
ちなみに clean タスク実行 → Rebuild Project 実行 → build タスクを実行するとこうなります。
履歴
2018/06/04
初版発行。