Spring Boot + npm + Geb で入力フォームを作ってテストする ( その62 )( 確認画面を作成する5 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その61 )( 確認画面を作成する4 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 確認画面の作成
- サーバ側のテストを作成します。
参照したサイト・書籍
- How to convert clob to string using Groovy?
https://stackoverflow.com/questions/27070403/how-to-convert-clob-to-string-using-groovy
目次
- GreenMail を導入する
- confirm.html の各項目に id 属性を付ける
- 送信したメールの本文を assert する時に使用するファイルを作成する
- InquiryConfirmController クラスのテストを作成する
- メモ書き+次回は。。。
手順
GreenMail を導入する
メール送信のテストをするために GreenMail を導入します。
build.gradle の以下の点を変更します。
dependencies { .......... // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの .......... testCompile("com.icegreen:greenmail:1.5.7") // for lombok
testCompile("com.icegreen:greenmail:1.5.7")
を追加します。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
次にテスト時に GreenMail のサーバを起動/停止するためのクラスを ksbysample-webapp-lending からコピーします。
src/test/java/ksbysample/common/test の下に rule/mail パッケージを作成した後、MailServerResource.java ダウンロードして配置します。配置後、@Component
アノテーションがあると Spock から使用できないので削除します。
confirm.html の各項目に id 属性を付ける
確認画面のテストをしやすくするために 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 id="name" th:text="*{name}">田中 太郎</td> </tr> <tr> <th nowrap>お名前(かな)</th> <td id="kana" th:text="*{kana}">たなか たろう</td> </tr> <tr> <th nowrap>性別</th> <td id="sex" th:text="*{sex}">男性</td> </tr> <tr> <th nowrap>年齢</th> <td id="age"> <th:block th:text="*{age}">30</th:block> 歳 </td> </tr> <tr> <th nowrap>職業</th> <td id="job" 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 id="zipcode">〒 <th:block th:text="*{zipcode}">102-0072</th:block> </td> </tr> <tr> <th nowrap>住所</th> <td id="address" th:text="*{address}">東京都千代田区飯田橋1-1</td> </tr> <tr> <th nowrap>電話番号</th> <td id="tel" th:text="*{tel}">03-1234-5678</td> </tr> <tr> <th nowrap>メールアドレス</th> <td id="email" 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 id="type1" th:text="*{type1}">製品に関するお問い合わせ</td> </tr> <tr> <th nowrap>お問い合わせの種類2</th> <td id="type2" th:text="*{type2}">見積が欲しい</td> </tr> <tr> <th nowrap>お問い合わせの内容</th> <td id="inquiry" 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 id="survey"> <ul style="padding-left: 20px"> <li th:each="sv : *{survey}" 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>
- 各項目に表示されているデータを取得しやすいようにするために td タグに id 属性を付加します。
- 「アンケート」に記述している
th:each="sv : *{survey}"
を ul タグに書いていましたが、この位置だと li タグだけでなく ul タグも複数出力されていたので li タグへ移動します。
送信したメールの本文を assert する時に使用するファイルを作成する
src/test/resources/ksbysample/webapp/bootnpmgeb/web/inquiry の下に inquirymail-body_001.txt, inquirymail-body_002.txt を新規作成し、以下の内容を記述します。
■inquirymail-body_001.txt
問い合わせフォームから入力された内容は以下の通りです。 お名前(漢字):12345678901234567890 12345678901234567890 お名前(かな):あいうえおかきくけこさしすせそたちつてと なにぬねのはひふへほまみむめもあいうえお 性別:女性 年齢:999歳 職業:その他 郵便番号:〒102-0072 住所:東京都千代田区飯田橋1-1 電話番号:03-1234-5678 メールアドレス:taro.tanaka@sample.co.jp お問い合わせの種類1:製品に関するお問い合わせ お問い合わせの種類2:見積が欲しい、資料が欲しい、その他の問い合わせ お問い合わせの内容: これはテストです アンケート: ・選択肢1だけ長くしてみる ・選択肢2 ・選択肢3 ・選択肢4 ・選択肢5が少し長い ・選択肢6 ・選択肢7 ・8
■inquirymail-body_002.txt
問い合わせフォームから入力された内容は以下の通りです。 お名前(漢字):12345678901234567890 12345678901234567890 お名前(かな):あいうえおかきくけこさしすせそたちつてと なにぬねのはひふへほまみむめもあいうえお 性別:女性 年齢:999歳 職業: 郵便番号:〒102-0072 住所:東京都千代田区飯田橋1-1 電話番号: メールアドレス:taro.tanaka@sample.co.jp お問い合わせの種類1:製品に関するお問い合わせ お問い合わせの種類2:見積が欲しい、資料が欲しい、その他の問い合わせ お問い合わせの内容: これはテストです アンケート:
InquiryConfirmController クラスのテストを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmController.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。
src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmControllerTest.groovy が新規作成されるので、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.web.inquiry import groovy.sql.Sql import ksbysample.common.test.helper.TestHelper import ksbysample.common.test.rule.mail.MailServerResource import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper import ksbysample.webapp.bootnpmgeb.values.* 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.junit.Rule import org.junit.experimental.runners.Enclosed import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.core.io.ClassPathResource import org.springframework.http.MediaType import org.springframework.mock.web.MockHttpSession import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MvcResult import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.web.context.WebApplicationContext import org.yaml.snakeyaml.Yaml import spock.lang.Specification import javax.mail.internet.MimeMessage import javax.sql.DataSource import java.util.stream.Collectors import static ksbysample.common.test.matcher.HtmlResultMatchers.html import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @RunWith(Enclosed) class InquiryConfirmControllerTest { @SpringBootTest static class 確認画面のテスト extends Specification { private InquiryInput01Form inquiryInput01Form_001 = (InquiryInput01Form) new Yaml().load(getClass().getResourceAsStream("InquiryInput01Form_001.yaml")) private InquiryInput02Form inquiryInput02Form_001 = new InquiryInput02Form( zipcode1: "102" , zipcode2: "0072" , address: "東京都千代田区飯田橋1-1" , tel1: "03" , tel2: "1234" , tel3: "5678" , email: "taro.tanaka@sample.co.jp") private InquiryInput03Form inquiryInput03Form_001 = new InquiryInput03Form( type1: Type1Values.PRODUCT.value , type2: [Type2Values.ESTIMATE.value, Type2Values.CATALOGUE.value, Type2Values.OTHER.value] , inquiry: "これはテストです" , survey: ["1", "2", "3", "4", "5", "6", "7", "8"]) @Rule public MailServerResource mailServerResource = new MailServerResource() @Autowired private WebApplicationContext context @Autowired private DataSource dataSource @Autowired private ValuesHelper vh @Autowired private SurveyOptionsHelper soh @Autowired private InquiryDataDao inquiryDataDao MockMvc mockMvc Sql sql void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(context) .build() sql = new Sql(dataSource) sql.execute("truncate table INQUIRY_DATA") } void teardown() { sql.close() } def "入力画面1~3で全ての項目に入力した場合のテスト"() { when: "入力画面1で項目全てに入力して「次へ」ボタンをクリックする" MvcResult result = mockMvc.perform( TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/02")) .andReturn() MockHttpSession session = result.getRequest().getSession() and: "入力画面2で項目全てに入力して「次へ」ボタンをクリックする" mockMvc.perform(TestHelper.postForm("/inquiry/input/02?move=next", inquiryInput02Form_001) .session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/03")) and: "入力画面3で項目全てに入力して「次へ」ボタンをクリックする" mockMvc.perform(TestHelper.postForm("/inquiry/input/03?move=next", inquiryInput03Form_001) .session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/confirm")) then: "確認画面に入力画面1~3で入力したデータが表示される" mockMvc.perform(get("/inquiry/confirm").session(session)) .andExpect(status().isOk()) .andExpect(html("#name").text(inquiryInput01Form_001.lastname + " " + inquiryInput01Form_001.firstname)) .andExpect(html("#kana").text(inquiryInput01Form_001.lastkana + " " + inquiryInput01Form_001.firstkana)) .andExpect(html("#sex").text(vh.getText(SexValues, inquiryInput01Form_001.sex))) .andExpect(html("#age").text(inquiryInput01Form_001.age + " 歳")) .andExpect(html("#job").text(vh.getText(JobValues, inquiryInput01Form_001.job))) .andExpect(html("#zipcode").text("〒 " + inquiryInput02Form_001.zipcode1 + "-" + inquiryInput02Form_001.zipcode2)) .andExpect(html("#address").text(inquiryInput02Form_001.address)) .andExpect(html("#tel").text(inquiryInput02Form_001.tel1 + "-" + inquiryInput02Form_001.tel2 + "-" + inquiryInput02Form_001.tel3)) .andExpect(html("#email").text(inquiryInput02Form_001.email)) .andExpect(html("#type1").text(vh.getText(Type1Values, inquiryInput03Form_001.type1))) .andExpect(html("#type2").text([Type2Values.ESTIMATE.text , Type2Values.CATALOGUE.text , Type2Values.OTHER.text].stream().collect(Collectors.joining("、")))) .andExpect(html("#inquiry").text(inquiryInput03Form_001.inquiry)) .andExpect(html("#survey > ul > li").count(8)) .andExpect(html("#survey > ul > li:nth-of-type(1)").text( soh.selectItemList("survey").stream() .filter({ SurveyOptions surveyOptions -> StringUtils.equals(surveyOptions.itemValue, "1") }) .map { SurveyOptions surveyOptions -> surveyOptions.itemName } .findFirst().get())) and: "確認画面で「送信」ボタンをクリックする" mockMvc.perform(post("/inquiry/confirm/send").contentType(MediaType.APPLICATION_FORM_URLENCODED) .session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/complete/")) then: "DBとメールを確認する" def rows = sql.rows("SELECT * FROM INQUIRY_DATA") rows.size() == 1 rows[0]["lastname"] == "12345678901234567890" rows[0]["firstname"] == "12345678901234567890" rows[0]["lastkana"] == "あいうえおかきくけこさしすせそたちつてと" rows[0]["firstkana"] == "なにぬねのはひふへほまみむめもあいうえお" rows[0]["sex"] == "2" rows[0]["age"] == 999 rows[0]["job"] == "3" rows[0]["zipcode1"] == "102" rows[0]["zipcode2"] == "0072" rows[0]["address"] == "東京都千代田区飯田橋1-1" rows[0]["tel1"] == "03" rows[0]["tel2"] == "1234" rows[0]["tel3"] == "5678" rows[0]["email"] == "taro.tanaka@sample.co.jp" rows[0]["type1"] == "1" rows[0]["type2"] == "1,2,3" rows[0]["inquiry"].asciiStream.text == inquiryInput03Form_001.inquiry rows[0]["survey"] == "1,2,3,4,5,6,7,8" mailServerResource.messagesCount == 1 MimeMessage message = mailServerResource.firstMessage message.subject == "問い合わせフォームからお問い合わせがありました" message.content == new ClassPathResource("ksbysample/webapp/bootnpmgeb/web/inquiry/inquirymail-body_001.txt").inputStream.text } def "入力画面1~3で必須項目だけに入力した場合のテスト"() { when: "入力画面1で必須項目だけに入力して「次へ」ボタンをクリックする" inquiryInput01Form_001.job = StringUtils.EMPTY MvcResult result = mockMvc.perform( TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/02")) .andReturn() MockHttpSession session = result.getRequest().getSession() and: "入力画面2で必須項目だけに入力して「次へ」ボタンをクリックする" inquiryInput02Form_001.tel1 = StringUtils.EMPTY inquiryInput02Form_001.tel2 = StringUtils.EMPTY inquiryInput02Form_001.tel3 = StringUtils.EMPTY mockMvc.perform(TestHelper.postForm("/inquiry/input/02?move=next", inquiryInput02Form_001) .session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/03")) and: "入力画面3で必須項目だけに入力して「次へ」ボタンをクリックする" inquiryInput03Form_001.survey = [] mockMvc.perform(TestHelper.postForm("/inquiry/input/03?move=next", inquiryInput03Form_001) .session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/confirm")) then: "確認画面に入力画面1~3で入力したデータが表示される" mockMvc.perform(get("/inquiry/confirm").session(session)) .andExpect(status().isOk()) .andExpect(html("#name").text(inquiryInput01Form_001.lastname + " " + inquiryInput01Form_001.firstname)) .andExpect(html("#kana").text(inquiryInput01Form_001.lastkana + " " + inquiryInput01Form_001.firstkana)) .andExpect(html("#sex").text(vh.getText(SexValues, inquiryInput01Form_001.sex))) .andExpect(html("#age").text(inquiryInput01Form_001.age + " 歳")) .andExpect(html("#job").text(StringUtils.EMPTY)) .andExpect(html("#zipcode").text("〒 " + inquiryInput02Form_001.zipcode1 + "-" + inquiryInput02Form_001.zipcode2)) .andExpect(html("#address").text(inquiryInput02Form_001.address)) .andExpect(html("#tel").text(StringUtils.EMPTY)) .andExpect(html("#email").text(inquiryInput02Form_001.email)) .andExpect(html("#type1").text(vh.getText(Type1Values, inquiryInput03Form_001.type1))) .andExpect(html("#type2").text([Type2Values.ESTIMATE.text , Type2Values.CATALOGUE.text , Type2Values.OTHER.text].stream().collect(Collectors.joining("、")))) .andExpect(html("#inquiry").text(inquiryInput03Form_001.inquiry)) .andExpect(html("#survey > ul > li").count(0)) and: "確認画面で「送信」ボタンをクリックする" mockMvc.perform(post("/inquiry/confirm/send").contentType(MediaType.APPLICATION_FORM_URLENCODED) .session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/complete/")) then: "DBとメールを確認する" def rows = sql.rows("SELECT * FROM INQUIRY_DATA") rows.size() == 1 rows[0]["lastname"] == "12345678901234567890" rows[0]["firstname"] == "12345678901234567890" rows[0]["lastkana"] == "あいうえおかきくけこさしすせそたちつてと" rows[0]["firstkana"] == "なにぬねのはひふへほまみむめもあいうえお" rows[0]["sex"] == "2" rows[0]["age"] == 999 rows[0]["job"] == StringUtils.EMPTY rows[0]["zipcode1"] == "102" rows[0]["zipcode2"] == "0072" rows[0]["address"] == "東京都千代田区飯田橋1-1" rows[0]["tel1"] == StringUtils.EMPTY rows[0]["tel2"] == StringUtils.EMPTY rows[0]["tel3"] == StringUtils.EMPTY rows[0]["email"] == "taro.tanaka@sample.co.jp" rows[0]["type1"] == "1" rows[0]["type2"] == "1,2,3" rows[0]["inquiry"].asciiStream.text == inquiryInput03Form_001.inquiry rows[0]["survey"] == StringUtils.EMPTY mailServerResource.messagesCount == 1 MimeMessage message = mailServerResource.firstMessage message.subject == "問い合わせフォームからお問い合わせがありました" message.content == new ClassPathResource("ksbysample/webapp/bootnpmgeb/web/inquiry/inquirymail-body_002.txt").inputStream.text } } }
テストを実行すると1件エラーになりますね。。。
調べたところ、以下の原因でした。
- INQUIRY_DATA テーブルの survey に NOT NULL が指定されていました。必須ではないので不要でしたね。。。
- InquiryInput03Form クラスで type2 は
private List<String> type2 = new ArrayList<>();
と実装しているのに、survey はprivate List<String> survey
とだけ書いていて= new ArrayList<>();
を書いていなかったため、値がないと null となる場合があり、SessionData2ConfirmFormTypeMap クラス等でinquiryInput03Form.getSurvey().stream()
を呼び出しているところでエラーになっていました。
まず src/main/resources/db/migration/V1__init.sql の以下の点を変更します。
CREATE TABLE INQUIRY_DATA ( id INT AUTO_INCREMENT PRIMARY KEY, lastname VARCHAR(20) NOT NULL, firstname VARCHAR(20) NOT NULL, lastkana VARCHAR(20) NOT NULL, firstkana VARCHAR(20) NOT NULL, sex VARCHAR(1) NOT NULL, age INT NOT NULL, job VARCHAR(1), zipcode1 VARCHAR(3) NOT NULL, zipcode2 VARCHAR(4) NOT NULL, address VARCHAR(256) NOT NULL, tel1 VARCHAR(5), tel2 VARCHAR(4), tel3 VARCHAR(4), email VARCHAR(256), type1 VARCHAR(1) NOT NULL, type2 VARCHAR(1), inquiry TEXT NOT NULL, survey VARCHAR(1), update_date DATETIME NOT NULL );
- survey から
NOT NULL
を削除します。
次に src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput03Form.java の以下の点を変更します。
private List<String> survey = new ArrayList<>();
private List<String> survey
→private List<String> survey = new ArrayList<>();
に変更します。
再度テストを実行すると、今度は全て成功しました。
メモ書き+次回は。。。
久しぶりにメール送信のテストを書いたら時間かかってしまいました。また Groovy が相変わらず便利ですね。今回以下の点が良かったです。
- Clob に格納されたテキストの比較をどうするかで、以前は org.springframework.util.StreamUtils#copyToString を利用しましたが、Groovy だと InputStream を返すメソッドに
.text
を付けるだけで文字列が取得できました。 - Getter, Setter メソッドを Groovy から呼び出す場合、get, set を取り除いて先頭を小文字にしたプロパティとして呼び出せるので、記述が少しだけ完結にできます。例えば
inquiryData.getInquiry().getAsciiStream().getText()
はinquiryData.inquiry.asciiStream.text
のように書けます。
次回は、
- CSRF対策が入っているはずなのですが、MockMvc#perform を呼び出す時に
.with(csrf())
を付けていなくてもテストがなぜか成功していることにほとんど書き終えてから気づきました。気になったので調査してみます。 - InquiryInputControllerTest クラスに未実装のテストがあるので実装します。
履歴
2018/07/07
初版発行。