Spring bootで簡単なWebアプリを作ってみる(ロジック作成3)

こんにちは。船井総研デジタルのいっちーです。
前回の記事では、ロジック本体の実装まで行いました。

https://qiita.com/fsd_itchy/items/d03dbf9af769c7281196

今回は、前回の記事で「宿題」となっていたつぶやき投稿時のチェックロジックを組み込んで、最終的にDocker上ですべて完結するように仕上げていこうと思います。

# Serviceクラスの修正
Serviceクラスのつぶやき投稿ロジックに手を入れて、ねぎマスタに登録されているねぎの名称とつぶやき内容を突き合わせます。

<details><summary>つぶやき投稿ロジック修正</summary>

```diff_java:src/main/java/com/example/negitter/service/NegitterService.java

(略)

public List<String> registerNegiit(String name, String negiit) {
    List<String> errorMessages = new ArrayList<>();
    Date registerdDataTime = Date.from(Instant.now());
    MessageTbl messageTbl = new MessageTbl();
+    // 登録済みのねぎを使用しているかチェックする
+    List<NegiMst> negiMsts = negiMstMapper.findAll();
+    if (negiMsts.stream().anyMatch(negi -> negiit.indexOf(negi.getNegiName()) >= 0)) {
        try {
            messageTbl.setNegiMessage(negiit);
            messageTbl.setRegisterName(name);
            messageTbl.setRegisterDatetime(registerdDataTime);
            messageTblMapper.insert(messageTbl);
        } catch (Exception e) {
            errorMessages.add("つぶやき登録時にエラーが発生しました。");
        }
+    } else {
+        errorMessages.add("ねぎマスタに登録されているねぎでつぶやいてください！");
+    }
    return errorMessages;
}

(略)

```

</details>

# 動作確認

修正できたので、動作確認します。JUnitでテストクラスを作って動作確認してみます。

## テストクラスを作る

今回修正したregisterNegiitをテストできるテストクラスを作成します。

<details><summary>テストクラス</summary>

```java:app/src/test/java/com/example/negitter/service/NegitterServiceTest.java
public class NegitterServiceTest {

    @Mock
    NegiMstMapper negiMstMapper;

    @Mock
    MessageTblMapper messageTblMapper;

    @InjectMocks
    NegitterService negitterService;

    private AutoCloseable closable;

    @BeforeEach
    public void openMocks() {
        closable = MockitoAnnotations.openMocks(this);
    }

    @AfterEach
    public void releaseMocks() throws Exception {
        closable.close();
    }

    @Test
    void ねぎマスタが空の場合はエラーになる() {
        List<NegiMst> negiMsts = new ArrayList<>();
        when(negiMstMapper.findAll()).thenReturn(negiMsts);

        List<String> errorMessages = negitterService.registerNegiit("ねぎ博士", "深谷市に3万円以上のふるさと納税をすると深谷ねぎを模した印鑑がもらえるそうです。");
        assertEquals(errorMessages.size(), 1);
        assertEquals(errorMessages.get(0), "ねぎマスタに登録されているねぎでつぶやいてください！");

    }

    @Test
    void 登録されていないねぎでつぶやいた場合はエラーになる_ねぎ１件() {
        List<NegiMst> negiMsts = new ArrayList<>();
        NegiMst kujouNegi = new NegiMst();
        kujouNegi.setId(0);
        kujouNegi.setNegiName("九条ねぎ");
        negiMsts.add(kujouNegi);
        when(negiMstMapper.findAll()).thenReturn(negiMsts);

        List<String> errorMessages = negitterService.registerNegiit("ねぎ博士", "深谷市に3万円以上のふるさと納税をすると深谷ねぎを模した印鑑がもらえるそうです。");
        assertEquals(errorMessages.size(), 1);
        assertEquals(errorMessages.get(0), "ねぎマスタに登録されているねぎでつぶやいてください！");
    }

    @Test
    void 登録されてるねぎでつぶやいた場合はエラーにならない_ねぎ１件() {
        List<NegiMst> negiMsts = new ArrayList<>();
        NegiMst fukayaNegi = new NegiMst();
        fukayaNegi.setId(0);
        fukayaNegi.setNegiName("深谷ねぎ");
        negiMsts.add(fukayaNegi);
        MessageTbl negiit = new MessageTbl();
        negiit.setId(0);
        negiit.setRegisterName("ねぎ博士");
        negiit.setNegiMessage("深谷市に3万円以上のふるさと納税をすると深谷ねぎを模した印鑑がもらえるそうです。");
        negiit.setRegisterDatetime(new Date());
        when(negiMstMapper.findAll()).thenReturn(negiMsts);
        when(messageTblMapper.insert(negiit)).thenReturn(1);

        List<String> errorMessages = negitterService.registerNegiit("ねぎ博士", "深谷市に3万円以上のふるさと納税をすると深谷ねぎを模した印鑑がもらえるそうです。");
        assertEquals(errorMessages.size(), 0);
    }

    @Test
    void 登録されていないねぎでつぶやいた場合はエラーになる_ねぎ複数() {
        List<NegiMst> negiMsts = new ArrayList<>();
        NegiMst kujouNegi = new NegiMst();
        kujouNegi.setId(0);
        kujouNegi.setNegiName("九条ねぎ");
        negiMsts.add(kujouNegi);
        NegiMst iwatsuNegi = new NegiMst();
        iwatsuNegi.setId(1);
        iwatsuNegi.setNegiName("岩津ねぎ");
        negiMsts.add(iwatsuNegi);
        when(negiMstMapper.findAll()).thenReturn(negiMsts);

        List<String> errorMessages = negitterService.registerNegiit("ねぎ博士", "深谷市に3万円以上のふるさと納税をすると深谷ねぎを模した印鑑がもらえるそうです。");
        assertEquals(errorMessages.size(), 1);
        assertEquals(errorMessages.get(0), "ねぎマスタに登録されているねぎでつぶやいてください！");
    }

    @Test
    void 登録されてるねぎでつぶやいた場合はエラーにならない_ねぎ複数() {
        List<NegiMst> negiMsts = new ArrayList<>();
        NegiMst fukayaNegi = new NegiMst();
        fukayaNegi.setId(0);
        fukayaNegi.setNegiName("深谷ねぎ");
        negiMsts.add(fukayaNegi);
        NegiMst kujouNegi = new NegiMst();
        kujouNegi.setId(1);
        kujouNegi.setNegiName("九条ねぎ");
        negiMsts.add(kujouNegi);
        MessageTbl negiit = new MessageTbl();
        negiit.setId(0);
        negiit.setRegisterName("ねぎ博士");
        negiit.setNegiMessage("深谷市に3万円以上のふるさと納税をすると深谷ねぎを模した印鑑がもらえるそうです。");
        negiit.setRegisterDatetime(new Date());
        when(negiMstMapper.findAll()).thenReturn(negiMsts);
        when(messageTblMapper.insert(negiit)).thenReturn(1);

        List<String> errorMessages = negitterService.registerNegiit("ねぎ博士", "深谷市に3万円以上のふるさと納税をすると深谷ねぎを模した印鑑がもらえるそうです。");
        assertEquals(errorMessages.size(), 0);
    }

}

```

:::note info
メソッド名に2バイト文字を使うのは賛否あると思いますが、私個人としては「原則半角、テストメソッドに限っては例外的にOK」としています。会社・案件によってコーディング規約に違いがあると思いますので、目を通しておきましょう。
:::

</details>

実行してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3280429/c83b750e-453b-c42c-d654-3d79109f293f.png)

100%成功で終了しました。

## 画面からも動作確認

画面からも動作確認してみます。

![画面テスト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3280429/a50de828-a168-410f-e704-91ced1f2f984.png)

ねぎマスタが空の状態でつぶやいてみます。

![画面テスト2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3280429/c91865fe-da32-fed5-78b4-8b9b0a486242.png)

うまくいったようです。他のパターンも確認しておきましょう。

:::note info
テスト駆動開発だと、まずテストクラスを作ってから本体のロジックを開発…という流れになりますので、ロジックを実装してからテストクラスを作る…という流れに違和感を持った読者様もいらっしゃるかも知れません。
案件、現場によってテストコードの扱いは様々と思いますが、読者様の現場はいかがでしょうか？
:::

# APコンテナ上で実行できるように修正

それでは、仕上げとして、このアプリをDockerのAPコンテナで実行できるように修正していきましょう。

## DB向き先の修正

application.propertiesを修正し、DBの向き先をDBコンテナに変更します。

<details><summary>application.properties</summary>

```diff_properties:src/main/resources/application.properties
## DB setting
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-spring.datasource.url=jdbc:mysql://localhost/negitter?serverTimezone=Asia/Tokyo
+spring.datasource.url=jdbc:mysql://db/negitter?serverTimezone=Asia/Tokyo
spring.datasource.username=user
spring.datasource.password=pass
spring.jpa.database=MYSQL

## MyBatis setting
mybatis.configuration.map-underscore-to-camel-case=true
```

</details>

## pom.xmlの修正

現在の設定ではJavaバージョン19でコンパイルする設定になっていますが、TomcatコンテナのJavaはバージョン17のため、バージョンを合わせます。また、デフォルト設定のままだとwarファイル名にバージョン番号が入ってしまうので、ファイル名の設定を追加します。

<details><summary>pom.xml</summary>

```diff_xml:pom.xml
## DB setting

(略)

<properties>
-	<java.version>19</java.version>
+	<java.version>17</java.version>
</properties>

(略)

<build>

(略)

+	<finalName>negitter</finalName>
</build>

(略)

```

</details>

## ビルドとデプロイ

warファイルを作成して、Webappsフォルダ配下に配置します。

```
/mnt/c/work/negitter/app$ mvn install
[INFO] Scanning for projects...

(略)

[INFO] 
[INFO] ------------------------< com.example:negitter >------------------------
[INFO] Building negitter 0.0.1-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------

(略)

[INFO] 
[INFO] --- maven-resources-plugin:3.3.0:resources (default-resources) @ negitter ---
[INFO] Copying 1 resource
[INFO] Copying 8 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ negitter ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-resources-plugin:3.3.0:testResources (default-testResources) @ negitter ---
[INFO] skip non existing resourceDirectory /mnt/c/work/negitter/app/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:testCompile (default-testCompile) @ negitter ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /mnt/c/work/negitter/app/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ negitter ---
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.negitter.NegitterApplicationTests

(略)

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v3.0.5-SNAPSHOT)

(略)

[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 31.922 s - in com.example.negitter.NegitterApplicationTests
[INFO] Running com.example.negitter.service.NegitterServiceTest
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.051 s - in com.example.negitter.service.NegitterServiceTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-war-plugin:3.3.2:war (default-war) @ negitter ---
[INFO] Packaging webapp
[INFO] Assembling webapp [negitter] in [/mnt/c/work/negitter/app/target/negitter]
[INFO] Processing war project
[INFO] Building war: /mnt/c/work/negitter/app/target/negitter.war
[INFO] 
[INFO] --- spring-boot-maven-plugin:3.0.5-SNAPSHOT:repackage (repackage) @ negitter ---

(略)

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:48 min
[INFO] Finished at: 2023-03-27T12:06:41+09:00
[INFO] ------------------------------------------------------------------------
/mnt/c/work/negitter/app$ 
```

![warつくった.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3280429/345ffb34-cf01-2e94-a066-d1ca7519709a.png)

warファイルが作られたので、これをWebapps配下にコピーします。

![warでぷろった.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3280429/05ecae5d-a18d-70ed-07c8-f6389c88b4b2.png)

warファイルが展開されたら、デプロイされるまで数分待ちます。

```
/mnt/c/work/negitter/docker$ docker logs -f ap

(略)

27-Mar-2023 03:18:44.219 INFO [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/negitter.war] has finished in [196,071] ms

```

ログからも、デプロイ完了が確認できました。


## 動作確認

[http://localhost/negitter](http://localhost/negitter)にアクセスして、画面が表示できることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3280429/959e1bd4-1ce9-daba-92fb-3f7e88ec1c8e.png)


画面が表示できたら、つぶやき・ねぎ登録・ねぎ更新ができるかどうか確かめてみましょう。

動作確認ができたら、今度こそ完成です。お疲れ様でした！

# まとめ
今回は、前回の宿題となっていたロジックの修正を行い、warファイルを作ってAPコンテナ上で実行できるところまで持っていきました。

次回はこのシリーズを振り返って、「総集編」として本記事の総括をしたいと思います。

それではまた。