📘 Vol.10.7：Service層×JUnitテストの基本とパターン 〜トランザクション・例外処理を含むServiceロジックを正しくテストする〜

## ✅ この回で学べること

- Service層のJUnitテストの基本構成  
- DAOやDBアクセスを含むServiceメソッドのテスト手法  
- H2 Databaseを使ったインメモリテストの実装例  
- モック化と実DBテストの使い分けパターン  

---

## 🧩 事前に準備するもの

- JUnit（JUnit 5 or 4）  
- H2 Database（組み込みDB）  
- DAOやDBユーティリティの共通化済みの状態（Vol.10.3まで参照）

---

## ✅ テスト構成イメージ（単体テスト）

```plaintext
[Test] UserServiceTest
     ↓ 呼び出し
[Service] UserService
     ↓ 呼び出し
[DAO] UserDao（H2DB用の実装 or モック）
```

---

### 📘 例1：H2データベースを用いたテスト（実DBテスト）

### ✅ pom.xml（H2とJUnit依存追加）

```xml
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>
```
---

### ✅ UserServiceTest.java

```java
public class UserServiceTest {
    private UserService userService;
    private DataSource dataSource;

    @Before
    public void setUp() throws Exception {
        // H2 in-memory DBの初期化
        JdbcDataSource ds = new JdbcDataSource();
        ds.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
        ds.setUser("sa");
        ds.setPassword("");
        this.dataSource = ds;

        // テーブル作成・テストデータ投入
        try (Connection conn = ds.getConnection();
             Statement stmt = conn.createStatement()) {
            stmt.execute("CREATE TABLE users (user_id INT PRIMARY KEY, username VARCHAR(100), email VARCHAR(100))");
            stmt.execute("CREATE TABLE user_roles (user_id INT, role VARCHAR(50))");
        }

        // DAOとServiceのセットアップ
        UserDao userDao = new UserDaoImpl();
        UserRoleDao userRoleDao = new UserRoleDaoImpl();
        this.userService = new UserService(userDao, userRoleDao, dataSource);
    }

    @Test
    public void testRegisterUser_success() throws Exception {
        User user = new User(1, "testuser", "test@example.com");
        userService.registerUser(user, "admin");

        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE user_id = ?")) {
            stmt.setInt(1, 1);
            ResultSet rs = stmt.executeQuery();
            assertTrue(rs.next());
            assertEquals("testuser", rs.getString("username"));
        }
    }
}
```

---

### ✅ Service層の例外テスト

```java
@Test(expected = ServiceException.class)
public void testRegisterUser_rollbackOnError() throws Exception {
    User user = new User(999, "errorUser", "invalid@example.com");

    // エラー発生をシミュレーションするため、userRoleDao側にエラー仕込みなども可能

    userService.registerUser(user, "invalid-role");

    // 登録されていないことを確認（ロールバックされている）
    try (Connection conn = dataSource.getConnection();
         PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE user_id = ?")) {
        stmt.setInt(1, 999);
        ResultSet rs = stmt.executeQuery();
        assertFalse(rs.next());
    }
}

```

---

### ✅ モックによるServiceテスト（Mockito使用）
H2を使わずにDAOをモックする場合：

```java
@Test
public void testRegisterUser_withMockDao() throws Exception {
    UserDao userDao = Mockito.mock(UserDao.class);
    UserRoleDao userRoleDao = Mockito.mock(UserRoleDao.class);
    DataSource ds = Mockito.mock(DataSource.class);
    Connection conn = Mockito.mock(Connection.class);

    when(ds.getConnection()).thenReturn(conn);

    UserService userService = new UserService(userDao, userRoleDao, ds);

    User user = new User(2, "mockuser", "mock@example.com");
    userService.registerUser(user, "admin");

    verify(userDao).insert(conn, user);
    verify(userRoleDao).assignRole(conn, 2, "admin");
}

```

---

### ✅ テスト方針の選択ガイド

| 方法           | 特徴                | 向いているケース                |
| ------------ | ----------------- | ----------------------- |
| H2 DBを使う     | 実際のSQLとDB動作を確認できる | SQL検証やトランザクション検証        |
| Mockitoでモック化 | DB依存なしで高速テスト      | 業務ロジックの分岐やエラー処理テスト中心の場合 |


---

### ✅ 今回のまとめ

| 項目          | ポイント                        |
| ----------- | --------------------------- |
| H2使用        | 実DBを使った統合に近い単体テストが可能        |
| モック使用       | DBに依存せずService層のロジックのみ検証できる |
| トランザクション・例外 | Service層の責務としてテスト対象に含めるべき   |

---

---

### ✨ シリーズまとめ（Vol.10.x バリデーション編）

- [✅ Vol.10.0：DAO / DTO / JavaBeansとは？ ― データ層設計の全体像と役割分担 ―](https://qiita.com/drafts/19c1d526df28240e0cb1/edit)

- [✅ Vol.10.1：DTO設計のベストプラクティス（命名規則・フィールド設計](https://qiita.com/juehara-crypto/items/1b0ad616095f8ae134ec)

- [✅ Vol.10.2：DAOのインタフェース設計と実装パターン ](https://qiita.com/juehara-crypto/items/bcb15618440bef5931ef)

- [✅ Vol.10.3：DB接続クラスの共通化とドライバ設計](https://qiita.com/juehara-crypto/items/c3d4e8e6887d5ec8e1eb)

- [✅ Vol.10.4：Actionとの連携実装（Struts2との融合）](https://qiita.com/juehara-crypto/items/b8873de1aa9e46cd9309)

- [✅ Vol.10.5：DAO・DTOのテスト設計（JUnitやH2活用）](https://qiita.com/juehara-crypto/items/9f0914697726068b9e93)

- [✅ Vol.10.6：Service層の設計方針と例外処理戦略 ](https://qiita.com/juehara-crypto/items/6685c303c40e48b3c1f3)

- [✅ Vol.10.8：Service層の共通基盤化とDI対応 ](https://qiita.com/juehara-crypto/items/8e94acf2a31be15aaf94)

---