読者です 読者をやめる 読者になる 読者になる

かんがるーさんの日記

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

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その47 )( 貸出承認画面の作成7 )

概要

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その46 )( 貸出承認画面の作成6 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 貸出承認画面の作成
      • テストの作成 ( 2回目 )

参照したサイト・書籍

  1. Get the Most out of Testing with Spring 4.2
    http://www.slideshare.net/SpringCentral/get-the-most-out-of-testing-with-spring-42-53128369

    • Spring の Blog ページ ( http://spring.io/blog ) で公開されていたドキュメントです。MockMvc で Spring Security のテストをする場合 MockMvc 生成時に .addFilter(springSecurityFilterChain) と書いていたのですが、このドキュメントの P.41 を見ると .apply(springSecurity()) に変わっているようですので、今回 SecurityMockMvcResource クラスの実装を変更します。

目次

  1. SecurityMockMvcResource クラスの変更
  2. LendingapprovalController クラスのテストの作成
  3. 全てのテストが成功するか確認する
  4. commit、Push、Pull Request、マージ
  5. 次回は。。。
  6. メモ書き

手順

SecurityMockMvcResource クラスの変更

  1. Spring Security のテストのために MockMvc を生成する時の実装方法を変更します。src/test/java/ksbysample/common/test の下の SecurityMockMvcResource.javaリンク先の内容 に変更します。

LendingapprovalController クラスのテストの作成

  1. src/main/java/ksbysample/webapp/lending/web/lendingapproval の下の LendingapprovalController.java で「Create Test」ダイアログを表示し、テストクラスを作成します。

    f:id:ksby:20160120172640p:plain

    src/test/java/ksbysample/webapp/lending/web/lendingapproval の下に LendingapprovalControllerTest.java が作成されます。

  2. 最初にテストの構成を決めます。src/test/java/ksbysample/webapp/lending/web/lendingapproval の下の LendingapprovalControllerTest.javaリンク先のその1の内容 に変更します。

  3. テストデータを作成します。src/test/resources/ksbysample/webapp/lending/web/lendingapproval の下に LendingapprovalForm_006.yaml を作成し、リンク先の内容 に変更します。

  4. src/test/resources/ksbysample/webapp/lending/web/lendingapproval の下に testdata/001 ディレクトリを作成します。

  5. src/test/resources/ksbysample/webapp/lending/web/lendingapproval/testdata/001 の下に lending_app, lending_book テーブルの CSV ファイルを作成します。Database tool で以下のデータを lending_app, lending_book テーブルに登録してから lending_app.csv, lending_book.csv というファイル名の CSV ファイルを出力します。また lending_app.csv, lending_book.csv を出力したディレクトリに test-order.txt も作成します。

    f:id:ksby:20160120211315p:plain

    CSVファイルを出力する手順は Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その39 )( 貸出申請画面の作成10 ) - LendingappController クラスのテストの作成 を見てください。

  6. lending_app.csv, lending_book.csv, test-order.txt は リンク先の内容 になります。

  7. 検証用のデータを作成します。src/test/resources/ksbysample/webapp/lending/web/lendingapproval の下に assertdata/001 と assertdata/002 ディレクトリを作成します。

  8. src/test/resources/ksbysample/webapp/lending/web/lendingapproval/testdata/001 の下の lending_app.csv, lending_book.csv, table-ordering.txt を src/test/resources/ksbysample/webapp/lending/web/lendingapproval/assertdata/001 の下へコピーした後、リンク先の内容 に変更します。

  9. src/test/resources/ksbysample/webapp/lending/web/lendingapproval/assertdata/001 の下に message.txt を作成します。作成後、リンク先の内容 に変更します。

  10. src/test/resources/ksbysample/webapp/lending/web/lendingapproval/testdata/001 の下の lending_app.csv, lending_book.csv, table-ordering.txt を src/test/resources/ksbysample/webapp/lending/web/lendingapproval/assertdata/002 の下へコピーした後、リンク先の内容 に変更します。

  11. src/test/resources/ksbysample/webapp/lending/web/lendingapproval/assertdata/002 の下に message.txt を作成します。作成後、リンク先の内容 に変更します。

  12. テストを実装します。src/test/java/ksbysample/webapp/lending/web/lendingapproval の下の LendingapprovalControllerTest.javaリンク先のその2の内容 に変更します。

  13. テストを実行します。LendingapprovalControllerTest クラスのクラス名の左側に表示されているアイコンをクリックしてコンテキストメニューを表示後「Run 'LendingapprovalControllerTest' with Coverage」を選択します。

    テストが成功することが確認できます。

    f:id:ksby:20160122005211p:plain

全てのテストが成功するか確認する

  1. 最後に全てのテストが成功するか確認します。Project View のルートでコンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択します。

    テストが実行され、全て成功することが確認できます。

    f:id:ksby:20160122005622p:plain

  2. clean タスクの実行→「Rebuild Project」メニューの実行→build タスクの実行を行います。

    "BUILD SUCCESSFUL" のメッセージが出力されることが確認できます。

    f:id:ksby:20160122010030p:plain

commit、Push、Pull Request、マージ

  1. ここまでの変更内容を commit します。

  2. GitHub へ Push、feature/53-issue -> 1.0.x へ Pull Request、1.0.x でマージ、feature/53-issue ブランチを削除、をします。

次回は。。。

  • 以下のソフトウェアがバージョアンアップしているので、バージョンアップを実施します。
  • 次に何点か改善したいと思ったことを Issue に書いたので、それらに対応します。
  • その後で貸出申請結果確認画面の作成に進む予定です。

メモ書き

最近記事を書いていて以下の2点のツールが欲しいなあと思っています。

  • IntelliJ IDEA 内で Java のソース ( src/main/java ) と、Thymeleaf テンプレートファイル ( src/main/resources/templates ) と、テストクラス ( src/test/groovy, src/test/java ) と、テストデータ ( src/test/resources ) 間を手軽にジャンプできる Plugin。
  • Form クラスからテストデータ用の YAML ファイルを生成してくれるツール。項目が多くなるとクラスを見ながら手で作成するのはちょっと手間なので。

IntelliJ IDEA の Plugin Development Guidelines ( https://www.jetbrains.com/idea/help/plugin-development-guidelines.html ) のページを見つけたのでリンクを書いておきます。

ソースコード

SecurityMockMvcResource.java

package ksbysample.common.test;

import org.junit.rules.ExternalResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

@Component
public class SecurityMockMvcResource extends ExternalResource {

    public final String MAILADDR_TANAKA_TARO = "tanaka.taro@sample.com";
    public final String MAILADDR_SUZUKI_HANAKO = "suzuki.hanako@test.co.jp";
    public final String MAILADDR_KIMURA_MASAO = "kimura.masao@test.co.jp";
    public final String MAILADDR_ENDO_YOKO = "endo.yoko@sample.com";
    public final String MAILADDR_SATO_MASAHIKO = "sato.masahiko@sample.com";
    public final String MAILADDR_TAKAHASI_NAOKO = "takahasi.naoko@test.co.jp";
    public final String MAILADDR_ITO_AOI = "ito.aoi@test.co.jp";

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private UserDetailsService userDetailsService;

    public MockMvc authTanakaTaro;
    public MockMvc authSuzukiHanako;
    public MockMvc authItoAoi;
    public MockMvc noauth;

    @Override
    protected void before() throws Throwable {
        // 認証ユーザ用MockMvc ( user = tanaka.taro@sample.com )
        UserDetails userDetailsTanakaTaro = userDetailsService.loadUserByUsername(MAILADDR_TANAKA_TARO);
        this.authTanakaTaro = MockMvcBuilders.webAppContextSetup(this.context)
                .defaultRequest(get("/").with(user(userDetailsTanakaTaro)))
                .apply(springSecurity())
                .build();

        // 認証ユーザ用MockMvc ( user = suzuki.hanako@test.co.jp )
        UserDetails userDetailsSuzukiHanako = userDetailsService.loadUserByUsername(MAILADDR_SUZUKI_HANAKO);
        this.authSuzukiHanako = MockMvcBuilders.webAppContextSetup(this.context)
                .defaultRequest(get("/").with(user(userDetailsSuzukiHanako)))
                .apply(springSecurity())
                .build();

        // 認証ユーザ用MockMvc ( user = ito.aoi@test.co.jp )
        UserDetails userDetailsItoAoi = userDetailsService.loadUserByUsername(MAILADDR_ITO_AOI);
        this.authItoAoi = MockMvcBuilders.webAppContextSetup(this.context)
                .defaultRequest(get("/").with(user(userDetailsItoAoi)))
                .apply(springSecurity())
                .build();

        // 非認証ユーザ用MockMvc
        this.noauth = MockMvcBuilders.webAppContextSetup(this.context)
                .apply(springSecurity())
                .build();
    }

}
  • @Autowired private Filter springSecurityFilterChain; を削除します。
  • before メソッドで MockMvcBuilders を呼び出して MockMvc を生成する時に呼び出すメソッド.addFilter(springSecurityFilterChain).apply(springSecurity()) へ変更します。

LendingapprovalControllerTest.java

■その1

package ksbysample.webapp.lending.web.lendingapproval;

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 LendingapprovalControllerTest {

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = Application.class)
    @WebAppConfiguration
    public static class 貸出承認画面の初期表示のテスト_エラー処理_DBなし {

        @Test
        public void ログインしていなければ貸出承認画面は表示できない() throws Exception {
        }

        @Test
        public void 承認権限を持たないユーザは貸出承認画面を表示できない() 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 貸出承認画面の初期表示のテスト_エラー処理_DBあり {

        @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 一部の書籍は承認却下未選択で一部の書籍は却下理由未入力の場合は入力チェックエラー() throws Exception {
        }

        @Test
        public void LendingapprovalForm_BeanValidationのテスト() 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.lendingapproval;

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 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 LendingapprovalControllerTest {

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = Application.class)
    @WebAppConfiguration
    public static class 貸出承認画面の初期表示のテスト_エラー処理_DBなし {

        @Rule
        @Autowired
        public SecurityMockMvcResource mvc;

        @Autowired
        private MessagesPropertiesHelper messagesPropertiesHelper;

        @Test
        public void ログインしていなければ貸出承認画面は表示できない() throws Exception {
            mvc.noauth.perform(get("/lendingapproval?lendingAppId=105"))
                    .andExpect(status().isFound())
                    .andExpect(redirectedUrl("http://localhost/"));
        }

        @Test
        public void 承認権限を持たないユーザは貸出承認画面を表示できない() throws Exception {
            mvc.authItoAoi.perform(get("/lendingapproval?lendingAppId=105"))
                    .andExpect(status().isForbidden());
        }

        @Test
        public void lendingAppIdパラメータがなければエラーになる() throws Exception {
            MvcResult result = mvc.authSuzukiHanako.perform(get("/lendingapproval"))
                    .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("LendingapprovalParamForm.lendingAppId.emptyerr", null));
        }

        @Test
        public void lendingAppIdパラメータで指定された値が数値でなければエラーになる() throws Exception {
            MvcResult result = mvc.authSuzukiHanako.perform(get("/lendingapproval?lendingAppId=xxx"))
                    .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("LendingapprovalParamForm.lendingAppId.emptyerr", null));
        }

    }

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = Application.class)
    @WebAppConfiguration
    public static class 貸出承認画面の初期表示のテスト_エラー処理_DBあり {

        @Rule
        @Autowired
        public TestDataResource testDataResource;

        @Rule
        @Autowired
        public SecurityMockMvcResource mvc;

        @Autowired
        private MessagesPropertiesHelper messagesPropertiesHelper;

        @Test
        public void lendingAppIdパラメータで指定されたデータが登録されていなければエラーになる() throws Exception {
            mvc.authSuzukiHanako.perform(get("/lendingapproval?lendingAppId=106"))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("text/html;charset=UTF-8"))
                    .andExpect(view().name("lendingapproval/lendingapproval"))
                    .andExpect(model().hasErrors())
                    .andExpect(model().errorCount(1))
                    .andExpect(errors().hasGlobalError("lendingapprovalForm", "LendingapprovalForm.lendingApp.nodataerr"));
        }
        
    }

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = Application.class)
    @WebAppConfiguration
    public static class 貸出承認画面の初期表示のテスト_正常処理 {

        @Rule
        @Autowired
        public TestDataResource testDataResource;

        @Rule
        @Autowired
        public SecurityMockMvcResource mvc;

        @Test
        @TestData("src/test/resources/ksbysample/webapp/lending/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(xpath("//*[@id=\"lendingapprovalForm\"]/div/div/table/tbody/tr").nodeCount(3))
                    .andExpect(xpath("//*[@id=\"lendingapprovalForm\"]/div/div/table/tbody/tr[1]/td[2]").string("978-4-7741-5377-3"))
                    .andExpect(xpath("//*[@id=\"lendingapprovalForm\"]/div/div/table/tbody/tr[1]/td[3]").string("JUnit実践入門"))
                    .andExpect(xpath("//*[@id=\"lendingapprovalForm\"]/div/div/table/tbody/tr[1]/td[4]").string("開発で使用する為"))
                    .andExpect(xpath("//*[@id=\"lendingapprovalForm\"]/div/div/table/tbody/tr[1]/td[6]/input[@type=\"text\"]").exists());
        }

    }

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = Application.class)
    @WebAppConfiguration
    public static class 貸出承認画面の入力チェックエラーのテスト {

        // テストデータ
        private LendingapprovalForm lendingapprovalForm_003
                = (LendingapprovalForm) new Yaml().load(getClass().getResourceAsStream("LendingapprovalForm_003.yaml"));
        private LendingapprovalForm lendingapprovalForm_006
                = (LendingapprovalForm) new Yaml().load(getClass().getResourceAsStream("LendingapprovalForm_006.yaml"));

        @Rule
        @Autowired
        public SecurityMockMvcResource mvc;

        // FormValidator の入力チェックを呼び出せているかチェックできればよいので、1パターンだけテストする 
        @Test
        public void 一部の書籍は承認却下未選択で一部の書籍は却下理由未入力の場合は入力チェックエラー() throws Exception {
            mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapproval/complete", this.lendingapprovalForm_003).with(csrf()))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("text/html;charset=UTF-8"))
                    .andExpect(view().name("lendingapproval/lendingapproval"))
                    .andExpect(model().hasErrors())
                    .andExpect(model().errorCount(2))
                    .andExpect(errors().hasGlobalError("lendingapprovalForm", "LendingapprovalForm.applyingBookFormList.approvalResult.notAllCheckedErr"))
                    .andExpect(errors().hasFieldError("lendingapprovalForm", "applyingBookFormList[2].approvalReason", ""));
        }

        @Test
        public void LendingapprovalForm_BeanValidationのテスト() throws Exception {
            mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapproval/complete", this.lendingapprovalForm_006).with(csrf()))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("text/html;charset=UTF-8"))
                    .andExpect(view().name("lendingapproval/lendingapproval"))
                    .andExpect(model().hasErrors())
                    .andExpect(model().errorCount(2))
                    .andExpect(errors().hasFieldError("lendingapprovalForm", "applyingBookFormList[0].approvalResult", "ValuesEnum"))
                    .andExpect(errors().hasFieldError("lendingapprovalForm", "applyingBookFormList[1].approvalReason", "Size"));
        }

    }

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = Application.class)
    @WebAppConfiguration
    public static class 貸出承認画面の正常処理時のテスト {

        // テストデータ
        private LendingapprovalForm lendingapprovalForm_004
                = (LendingapprovalForm) new Yaml().load(getClass().getResourceAsStream("LendingapprovalForm_004.yaml"));
        private LendingapprovalForm lendingapprovalForm_005
                = (LendingapprovalForm) new Yaml().load(getClass().getResourceAsStream("LendingapprovalForm_005.yaml"));

        @Rule
        @Autowired
        public TestDataResource testDataResource;

        @Autowired
        private DataSource dataSource;

        @Rule
        @Autowired
        public MailServerResource mailServerResource;

        @Rule
        @Autowired
        public SecurityMockMvcResource mvc;
        
        @Test
        @TestData("src/test/resources/ksbysample/webapp/lending/web/lendingapproval/testdata/001")
        public void 確定ボタンをクリックした場合_承認() throws Exception {
            // when ( Spock Framework のブロックの区分けが分かりやすかったので、同じ部分にコメントで付けてみました )
            mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapproval/complete", this.lendingapprovalForm_004).with(csrf()))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("text/html;charset=UTF-8"))
                    .andExpect(view().name("lendingapproval/lendingapproval"))
                    .andExpect(model().hasNoErrors())
                    .andExpect(xpath("//*[@id=\"lendingapprovalForm\"]/div/div/table/tbody/tr[1]/td[6]/input[@type=\"text\"]").doesNotExist());

            // then ( Spock Framework のブロックの区分けが分かりやすかったので、同じ部分にコメントで付けてみました )
            // DB
            IDataSet dataSet = new CsvDataSet(new File("src/test/resources/ksbysample/webapp/lending/web/lendingapproval/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_app_id", "isbn,book_name", "lending_state", "lending_app_flg", "lending_app_reason", "approval_reason"});
            // メール
            assertThat(mailServerResource.getMessagesCount()).isEqualTo(1);
            MimeMessage mimeMessage = mailServerResource.getFirstMessage();
            assertThat(mimeMessage.getRecipients(javax.mail.Message.RecipientType.TO))
                    .extracting(Object::toString)
                    .containsOnly("tanaka.taro@sample.com");
            assertThat(mimeMessage.getContent())
                    .isEqualTo(com.google.common.io.Files.toString(
                            new File("src/test/resources/ksbysample/webapp/lending/web/lendingapproval/assertdata/001/message.txt")
                            , Charsets.UTF_8));
        }

        @Test
        @TestData("src/test/resources/ksbysample/webapp/lending/web/lendingapproval/testdata/001")
        public void 確定ボタンをクリックした場合_却下と却下理由() throws Exception {
            // when ( Spock Framework のブロックの区分けが分かりやすかったので、同じ部分にコメントで付けてみました )
            mvc.authTanakaTaro.perform(TestHelper.postForm("/lendingapproval/complete", this.lendingapprovalForm_005).with(csrf()))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("text/html;charset=UTF-8"))
                    .andExpect(view().name("lendingapproval/lendingapproval"))
                    .andExpect(model().hasNoErrors())
                    .andExpect(xpath("//*[@id=\"lendingapprovalForm\"]/div/div/table/tbody/tr[1]/td[6]/input[@type=\"text\"]").doesNotExist())
                    .andExpect(xpath("//*[@id=\"lendingapprovalForm\"]/div[2]/div/table/tbody/tr[1]/td[6]/span").string("購入済です"));

            // then ( Spock Framework のブロックの区分けが分かりやすかったので、同じ部分にコメントで付けてみました )
            // DB
            IDataSet dataSet = new CsvDataSet(new File("src/test/resources/ksbysample/webapp/lending/web/lendingapproval/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_app_id", "isbn,book_name", "lending_state", "lending_app_flg", "lending_app_reason"});
            // メール
            assertThat(mailServerResource.getMessagesCount()).isEqualTo(1);
            MimeMessage mimeMessage = mailServerResource.getFirstMessage();
            assertThat(mimeMessage.getRecipients(javax.mail.Message.RecipientType.TO))
                    .extracting(Object::toString)
                    .containsOnly("tanaka.taro@sample.com");
            assertThat(mimeMessage.getContent())
                    .isEqualTo(com.google.common.io.Files.toString(
                            new File("src/test/resources/ksbysample/webapp/lending/web/lendingapproval/assertdata/002/message.txt")
                            , Charsets.UTF_8));
        }

    }

}

LendingapprovalForm_006.yaml

!!ksbysample.webapp.lending.web.lendingapproval.LendingapprovalForm
# 「確定」ボタンが押された場合
# 1件目は approvalResult のコードが 1, 2 以外にしている、2件目は却下理由が 128文字を超えている ( 129文字 )
lendingApp: 
  lendingAppId: 105
  status: 3
  lendingUserId: 1
  approvalUserId: 
  version: 1
username: temporarySave
applyingBookFormList: 
  - lendingBookId: 522
    isbn: 978-4-7741-6366-6
    bookName: GitHub実践入門
    lendingAppReason: 開発で使用する為
    approvalResult: 3
    approvalReason: 
    version: 1
  - lendingBookId: 524
    isbn: 978-4-7741-5377-3
    bookName: JUnit実践入門
    lendingAppReason: 勉強の為
    approvalResult: 2
    approvalReason: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    version: 1
  - lendingBookId: 525
    isbn: 978-4-7973-8014-9
    bookName: Java最強リファレンス
    lendingAppReason: 勉強会の調査の為
    approvalResult: 1
    approvalReason: 
    version: 1

lendingapproval/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,3,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
522,105,978-4-7741-5377-3,JUnit実践入門,蔵書あり,1,開発で使用する為,[null],[null],1
523,105,978-4-7973-8014-9,Java最強リファレンス,蔵書あり,[null],[null],[null],[null],1
524,105,978-4-7973-4778-4,アジャイルソフトウェア開発の奥義,蔵書あり,1,勉強の為,[null],[null],1
525,105,978-4-87311-704-1,Javaによる関数型プログラミング,蔵書あり,1,勉強会の調査の為,[null],[null],1

■test-order.txt

lending_app
lending_book

lendingapproval/assertdata/001/lending_app.csv, lending_book.csv, message.txt

■lending_app.csv

lending_app_id,status,lending_user_id,approval_user_id,version
105,4,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,開発で使用する為,1,[null],2
523,105,978-4-7973-8014-9,Java最強リファレンス,蔵書あり,[null],[null],[null],[null],1
524,105,978-4-7973-4778-4,アジャイルソフトウェア開発の奥義,蔵書あり,1,勉強の為,1,[null],2
525,105,978-4-87311-704-1,Javaによる関数型プログラミング,蔵書あり,1,勉強会の調査の為,1,[null],2

■message.txt

貸出申請が承認・却下されました。
========================================================================
承認/却下 書籍
------------------------------------------------------------------------
 承認   GitHub実践入門
 承認   JUnit実践入門
 承認   Java最強リファレンス
========================================================================

詳細は以下のURLから確認してください。

http://localhost:8080/confirmresult?lendingAppId=105

lendingapproval/assertdata/002/lending_app.csv, lending_book.csv, message.txt

■lending_app.csv

lending_app_id,status,lending_user_id,approval_user_id,version
105,4,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,開発で使用する為,2,購入済です,2
523,105,978-4-7973-8014-9,Java最強リファレンス,蔵書あり,[null],[null],[null],[null],1
524,105,978-4-7973-4778-4,アジャイルソフトウェア開発の奥義,蔵書あり,1,勉強の為,2,自分で購入して勉強しましょう,2
525,105,978-4-87311-704-1,Javaによる関数型プログラミング,蔵書あり,1,勉強会の調査の為,2,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,2

■message.txt

貸出申請が承認・却下されました。
========================================================================
承認/却下 書籍
------------------------------------------------------------------------
 却下   GitHub実践入門
 却下   JUnit実践入門
 却下   Java最強リファレンス
========================================================================

詳細は以下のURLから確認してください。

http://localhost:8080/confirmresult?lendingAppId=105

履歴

2016/01/20
初版発行。