Spring Boot + JUnit + h2でスタンドアローンな機能テストをやる

[Spring Boot + MySQLでシンプルなWeb REST APIサーバを実装する - Qiita](https://qiita.com/YutaKase6/items/007f6b7d12d342c1330c)

# Outline
Spring bootで作成したREST APIの機能テストをやる。
httpリクエストを送信し、レスポンスと、DBの状態をテストする。

インメモリDB(H2)を利用することで、DBサーバが無くても実行可能なテストを組む。

# H2
[H2 Database Engine](http://www.h2database.com/html/main.html)
<img src="http://www.h2database.com/html/images/h2-logo-2.png">

# 準備
## dependency
プロジェクトの依存にH2を追加する。

```build.gradle
testCompile("com.h2database:h2:${h2Version}")
```

## 接続設定
`src/test/resources/application.yml`
ここに置くことで、テスト時に読み込まれるprofileを定義できる。
テスト時はMySQLではなく、H2の設定を書く。

```application.yml
spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:;DB_CLOSE_ON_EXIT=TRUE;MODE=MySQL
    username: sa
    password:

  jpa:
    hibernate:
      ddl-auto: none
```
## DDL
`src/test/resources/schema.sql`
Spring Bootアプリケーションでは、schema.sqlというファイル名のスクリプトは起動時に実行される。
DDLを書いておく。

```sql
CREATE TABLE IF NOT EXISTS test_users (
  id VARCHAR(18) NOT NULL
  , value VARCHAR(255) DEFAULT NULL
  , created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
  , updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
  , PRIMARY KEY(id)
);
```

## DML
`src/test/resources/data.sql`
schema.sql同様、data.sqlというファイルも起動時に実行される。
テストに利用するデータを起動時に挿入しておく。

```data.sql
INSERT INTO test_users ( id, value ) VALUES ( 'test_id', 'test_value' );
```

# UserApiTests.java
ユーザ登録のテストのみ抜粋

```UserApiTests.java
︙

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserApiTests {
    private final static String TABLE = "test_users";
    private static final String SELECT_SQL = String.format("SELECT * FROM %s WHERE ID = ?", TABLE);

    private static final String BASE_PATH = "/v1/users/";

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private TestRestTemplate testRestTemplate;

    @Test
    public void postUser() throws URISyntaxException {
        String body = "{\"id\":\"test_post_id\",\"value\":\"test_post_value\"}";
        String exceptedResp = body;

        RequestEntity req = RequestEntity
                .post(new URI(BASE_PATH))
                .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                .body(body);
        ResponseEntity actualResp = this.testRestTemplate.exchange(req, String.class);

        assertThat(actualResp.getBody().toString()).isEqualTo(exceptedResp);
        assertThat(actualResp.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        Map<String, Object> actualData = this.jdbcTemplate.queryForMap(SELECT_SQL, "test_post_id");
        assertThat(actualData.get("id")).isEqualTo("test_post_id");
        assertThat(actualData.get("value")).isEqualTo("test_post_value");
    }

︙
```

## SpringBootTest
@SpringBootTestを付与することで、Springアプリケーションが起動する。
これにより、DIコンテナなどSpringの機能が利用可能となる。
そのぶんテスト実行に時間がかかるので単体テストとかでペタペタ貼らないほうがいい気がする。


## Http通信部分
@SpringBootTestの引数を以下のように設定することで、空いているポートでWebサーバが起動する。

```java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
```

このポートにアクセスする方法はいくつかあるが、今回はTestRestTemplateを使う
インタフェースなどRestTemplateとほぼ変わらない。
指定するURIにホスト名などを含めず、ベースパス以降にするだけで、テスト用に立ちたがっているアプリケーションと通信する。
[Spring Boot Reference Guide](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-rest-templates-test-utility)
インスタンスはDIコンテナ経由で取得可能。

```java
@Autowired
private TestRestTemplate testRestTemplate;
```

## DB部分
JdbcTemplateを利用して、DBの状態を取得する。
これもDIコンテナ経由でインスタンスが取得可能。

queryForMapというインタフェースでMap形式でレコードを参照可能。

```java
Map<String, Object> actualData = this.jdbcTemplate.queryForMap(SELECT_SQL, "test_post_id");
assertThat(actualData.get("id")).isEqualTo("test_post_id");
assertThat(actualData.get("value")).isEqualTo("test_post_value");
```

ちなみに0件をnullとかでチェックすることはできないようなので、そういうときはListを使ってる。

```java
List<Map<String, Object>> actualDataList = this.jdbcTemplate.queryForList(SELECT_SQL, "test_empty_id");
assertThat(actualDataList.size()).isEqualTo(0);
```