かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その6 )( Thymeleaf を 2.1.5 → 3.0.6 へバージョンアップする2 )

概要

記事一覧はこちらです。

Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その5 )( Thymeleaf を 2.1.5 → 3.0.6 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回に続き Thymeleaf 3 へのバージョンアップを行います。

参照したサイト・書籍

  1. Read Input hidden tag value from url of website using Jsoup in java
    https://stackoverflow.com/questions/13071165/read-input-hidden-tag-value-from-url-of-website-using-jsoup-in-java

目次

  1. mainparts.html の head タグを th:fragment 化し、head-cssjs.html を削除する(その2)
  2. LibraryHelper#getSelectedLibrary を変更する
  3. 全ての各画面用 Thymeleaf テンプレートに <!--/*@thymesVar id="..." type="..."*/--> を記述する
  4. WARN ログ [THYMELEAF][main] Template Mode 'HTML5' is deprecated. Using Template Mode 'HTML' instead. が出力されないようにする
  5. clean タスク → Rebuild Project → build タスクを実行する
  6. メモ書き
    1. th:block は末尾に “/” が必ず必要
    2. .andExpect(...) は lambda 式で .andExpect(mvcResult -> {...}) と書けば AssertJ が利用できる

手順

mainparts.html の head タグを th:fragment 化し、head-cssjs.html を削除する(その2)

  1. 残りの各画面用 Thymeleaf テンプレートを変更します。ファイル数が多いので、変更内容は書きません。また全て src/main/resources の下のファイルです。

    • templates/booklist/booklist.html
    • templates/booklist/complete.html
    • templates/booklist/fileupload.html
    • templates/confirmresult/confirmresult.html
    • templates/lendingapp/lendingapp.html
    • templates/lendingapproval/lendingapproval.html
    • templates/sessionsample/confirm.html
    • templates/sessionsample/first.html
    • templates/sessionsample/next.html
    • templates/springmvcmemo/beanValidationGroup.html
    • templates/textareamemo/display.html
    • templates/textareamemo/index.html
    • templates/error.html
    • templates/login.html
    • templates/loginsuccess.html
  2. Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その26 )( jar ファイルを作成して動作確認する2 ) に記載した手順に従って画面を操作し、画面の表示がおかしなところがないことを確認します。

  3. テストで .andExpect(xpath(...)) を使用しているところを変更します。こちらも変更内容は書きません。全て src/test/java の下のファイルです。

    • ksbysample.webapp.lending.web.confirmresult.ConfirmresultControllerTest
    • ksbysample.webapp.lending.web.lendingapp.LendingappControllerTest
    • ksbysample.webapp.lending.web.lendingapproval.LendingapprovalControllerTest
  4. clean タスク → Rebuild Project → build タスクを実行して “BUILD SUCCESSFUL” が出力されることを確認します。

    f:id:ksby:20170524173206p:plain

LibraryHelper#getSelectedLibrary を変更する

  1. src/main/java/ksbysample/webapp/lending/helper/library/LibraryHelper.javaリンク先の内容 に変更します。

  2. src/test/java/ksbysample/webapp/lending/helper/library/LibraryHelperTest.javaリンク先の内容 に変更します。変更後、テストが成功することを確認します。

    f:id:ksby:20170524201500p:plain

  3. src/main/resources/templates/common/mainparts.html を リンク先の内容 に変更します。

  4. Tomcat を起動して、図書館未選択時に “※図書館が選択されていません"、選択時に "選択中:…” が表示されることを確認します(画面キャプチャは省略します)。

全ての各画面用 Thymeleaf テンプレートに <!--/*@thymesVar id="..." type="..."*/--> を記述する

  1. 以下の Thymeleaf テンプレートに <!--/*@thymesVar id="..." type="..."*/--> を追加します。変更内容は書きません。全て src/main/resources の下のファイルです。

    • templates/admin/library/library.html
    • templates/booklist/booklist.html
    • templates/booklist/fileupload.html
    • templates/confirmresult/confirmresult.html
    • templates/lendingapp/lendingapp.html
    • templates/lendingapproval/lendingapproval.html

    <!--/*@thymesVar id="..." type="..."*/--> の記述は IntelliJ IDEA だと以下の操作で自動挿入できます。th:object 等で書いている変数の <!--/*@thymesVar id="..." type="..."*/--> の記述がないと、以下の画像のように赤波線が表示されます。

    f:id:ksby:20170524205303p:plain

    赤波線が表示されている変数にカーソルを移動して Alt+Enter を押すとコンテキストメニューが表示されますので、「Declare external variable in comment annotation」を選択します。

    f:id:ksby:20170524205517p:plain

    <!--/*@thymesVar id="..." type="..."*/--> の記述が自動で挿入されますので、あとは type の部分を記入します。

    f:id:ksby:20170524205810p:plain

WARN ログ [THYMELEAF][main] Template Mode 'HTML5' is deprecated. Using Template Mode 'HTML' instead. が出力されないようにする

  1. src/main/resources/application.properties を リンク先の内容 に変更します。

clean タスク → Rebuild Project → build タスクを実行する

  1. 最後に clean タスク → Rebuild Project → build タスクを実行して、ここまでの変更で問題がないことを確認します。

    f:id:ksby:20170525001743p:plain

メモ書き

th:block は末尾に “/” が必ず必要

今回 src/main/resources/templates/common/mainparts.html を修正した時に、Thymeleaf 3 から末尾の “/” は不要になったと考えて、最初 <th:block th:replace="${links} ?: _"> と末尾に “/” を付けずに書いたのですが、これだときちんと動作しませんでした。

例えば「貸出希望書籍 CSV ファイルアップロード」画面(http://localhost:8080/booklist)を表示すると、<th:block th:replace="${links} ?: _"/> の時は、HTML は以下のように出力されますが、

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>貸出希望書籍 CSV ファイルアップロード</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <!-- Bootstrap 3.3.4 -->
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css">
    <!-- Font Awesome Icons -->
    <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css">
    <!-- Ionicons -->
    <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css">
    <!-- Theme style -->
    <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css">
    <!-- AdminLTE Skins. Choose a skin from the css/skins
         folder instead of downloading all of them to reduce the load. -->
    <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css">

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="/js/html5shiv.min.js"></script>
    <script src="/js/respond.min.js"></script>
    <![endif]-->

    <!-- ここに各htmlで定義された link タグが追加される -->
    <link href="/css/fileinput.min.css" rel="stylesheet" type="text/css">

    <style type="text/css">
        <!--
        .jp-gothic {
            font-family: Verdana, "游ゴシック", YuGothic, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
        }
        .content-wrapper {
            background-color: #fffafa;
        }
        .noselected-library {
            color: #ff8679 !important;
            font-size: 100%;
            font-weight: 700;
        }
        .selected-library {
            color: #ffffff !important;
            font-size: 100%;
            font-weight: 700;
        }
        .box-body.no-padding {
            padding-bottom: 10px !important;
        }
        .table>tbody>tr>td
        , .table>tbody>tr>th
        , .table>tfoot>tr>td
        , .table>tfoot>tr>th
        , .table>thead>tr>td
        , .table>thead>tr>th {
            padding: 5px;
            font-size: 90%;
        }
        .has-error {
            background-color: #ffcccc;
        }
        -->
    </style>

    <!-- ここに各htmlで定義された style タグが追加される -->
    <style type="text/css">
        <!--
        .callout ul li {
            margin-left: -30px;
        }
        -->
    </style>
</head>

<th:block th:replace="${links} ?: _"> のように末尾の “/” を付け忘れると、<style type="text/css"> から下が表示されなくなります。しかも何のエラーも出ません(画面の背景色が青くなったのでおかしいことに気づきました)。

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>貸出希望書籍 CSV ファイルアップロード</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <!-- Bootstrap 3.3.4 -->
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css">
    <!-- Font Awesome Icons -->
    <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css">
    <!-- Ionicons -->
    <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css">
    <!-- Theme style -->
    <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css">
    <!-- AdminLTE Skins. Choose a skin from the css/skins
         folder instead of downloading all of them to reduce the load. -->
    <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css">

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="/js/html5shiv.min.js"></script>
    <script src="/js/respond.min.js"></script>
    <![endif]-->

    <!-- ここに各htmlで定義された link タグが追加される -->
    <link href="/css/fileinput.min.css" rel="stylesheet" type="text/css"></head>

HTML の meta タグ等は末尾を /> にする必要はなくなりましたが、th:block<th:block ... /> のように必ず末尾に “/” を付けましょう。

.andExpect(...) は lambda 式で .andExpect(mvcResult -> {...}) と書けば AssertJ が利用できる

今回久しぶりに ResultMatcher のクラスを作成して気づきましたが、.andExpect(...) 内に指定する式は、

  • MvcResult 型の引数を1つ渡して、戻り値が void 型の lambda 式でよい。
  • lambda 式の中で AssertionError の例外が throw されればテストが失敗する。

ということに気づきました(org.springframework.test.web.servlet.ResultMatcher インターフェース参照)。

ということは、.andExpect(mvcResult -> {...}) の形式で書けば、AssertJ を利用することができます。例えば、src/test/java/ksbysample/webapp/lending/web/lendingapproval/LendingapprovalControllerTest.javalendingAppIdパラメータで指定されたデータが登録されており承認権限を持つユーザならば貸出承認画面が表示される() メソッドを以下のように変更して、

        @Test
        @TestData("web/lendingapproval/testdata/001")
        public void lendingAppIdパラメータで指定されたデータが登録されており承認権限を持つユーザならば貸出承認画面が表示される() throws Exception {
            mvc.authSuzukiHanako.perform(get("/lendingapproval?lendingAppId=105"))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("text/html;charset=UTF-8"))
                    .andExpect(view().name("lendingapproval/lendingapproval"))
                    .andExpect(model().hasNoErrors())
                    .andExpect(html("#lendingapprovalForm > div > div > table > tbody > tr").count(3))
                    .andExpect(mvcResult -> {
                        assertThat(Jsoup.parse(mvcResult.getResponse().getContentAsString())
                                .select("#lendingapprovalForm > div > div > table > tbody > tr > td:eq(1)"))
                                .extracting(Element::text)
                                .containsOnly("978-4-7741-5377-3", "978-4-7973-4778-4", "978-4-87311-704-1");
                    });
        }

テストを実行すると、問題なく成功します。

f:id:ksby:20170525010744p:plain

.containsOnly(...) に記述する要素を1つ削除してテストが失敗するように変更すると、

        @Test
        @TestData("web/lendingapproval/testdata/001")
        public void lendingAppIdパラメータで指定されたデータが登録されており承認権限を持つユーザならば貸出承認画面が表示される() throws Exception {
            mvc.authSuzukiHanako.perform(get("/lendingapproval?lendingAppId=105"))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("text/html;charset=UTF-8"))
                    .andExpect(view().name("lendingapproval/lendingapproval"))
                    .andExpect(model().hasNoErrors())
                    .andExpect(html("#lendingapprovalForm > div > div > table > tbody > tr").count(3))
                    .andExpect(mvcResult -> {
                        assertThat(Jsoup.parse(mvcResult.getResponse().getContentAsString())
                                .select("#lendingapprovalForm > div > div > table > tbody > tr > td:eq(1)"))
                                .extracting(Element::text)
                                .containsOnly("978-4-7741-5377-3", "978-4-7973-4778-4");
                    });
        }

テストが失敗して、かつ失敗の原因が表示されます。

f:id:ksby:20170525011022p:plain

.andExpect(...) の中で AssertJ を使うことは可能です、という話でした。

ソースコード

LibraryHelper.java

    public String getSelectedLibrary() {
        String result = null;
        LibraryForsearch libraryForsearch = libraryForsearchDao.selectSelectedLibrary();
        if (libraryForsearch != null) {
            result = "選択中:" + libraryForsearch.getFormal();
        }
        return result;
    }
  • getSelectedLibrary メソッドで図書館が選択されていない場合には “※図書館が選択されていません” ではなく null を返すようにします。

LibraryHelperTest.java

    @Test
    public void testGetSelectedLibrary_図書館が選択されていない場合() throws Exception {
        given(libraryForsearchDao.selectSelectedLibrary()).willReturn(null);

        String result = libraryHelper.getSelectedLibrary();
        assertThat(result).isNull();
    }
  • testGetSelectedLibrary_図書館が選択されていない場合() テストメソッド内の assertThat(result).isEqualTo("※図書館が選択されていません");assertThat(result).isNull(); ヘ変更します。

mainparts.html

                <!-- Navbar Right Menu -->
                <div class="navbar-custom-menu">
                    <p class="navbar-text"
                       th:with="selectedLibrary=${@libraryHelper.getSelectedLibrary()}"
                       th:classappend="${selectedLibrary} ? 'selected-library' : 'noselected-library'"
                       th:text="${selectedLibrary} ?: _">※図書館が選択されていません</p>
                    <ul class="nav navbar-nav">
                        <li><a href="/logout">ログアウト</a></li>
                    </ul>
                </div>
  • th:with="selectedLibrary=${@libraryHelper.getSelectedLibrary()}" を追加します。
  • th:classappend の中の ${#strings.startsWith(@libraryHelper.getSelectedLibrary(), '※')}${selectedLibrary} に変更します。また 'noselected-library' : 'selected-library''selected-library' : 'noselected-library' に変更します。
  • th:text の中の ${@libraryHelper.getSelectedLibrary()}${selectedLibrary} ?: _ に変更します。

application.properties

spring.freemarker.cache=true
spring.freemarker.settings.number_format=computer
spring.freemarker.charset=UTF-8
spring.freemarker.enabled=false
spring.freemarker.prefer-file-system-access=false

spring.thymeleaf.mode=HTML

valueshelper.classpath.prefix=
  • spring.thymeleaf.mode=HTML を追加します。

履歴

2017/05/25
初版発行。