IntelliJ+JerseyとPostmanでRESTfulAPIサンプル(JSON)

# 目的

IntelliJ IDEAで、JerseyでRESTfulAPIのサンプルを作る覚書。

# ゴール

- Tomcat起動してブラウザでAPIを呼び出す(GETまで)
- Postmanを使ってAPIの動作確認
- とりあえずJSONで返す

# 環境など

|ツールなど|バージョンなど|
|:--|:--|
| MacbookPro |macOS Mojave 10.14.5|
|IntelliJ IDEA |Ultimate 2019.3.3|
|Java |AdoptOpenJDK 11|
|apache maven |3.6.3|
|Jersey|2.30.1|
|JUnit|5.6.0|
|Tomcat|apache-tomcat-8.5.51|
|Postman|7.19.1|

jdkやmaven、Tomcatのインストールは済んでいるものとします。
※コマンドラインでmavenコマンドを使わない場合は、mavenのインストールは不要です。(IntelliJにバンドルされています)

Postmanは[こちら](https://www.postman.com/)からインストールできます。
かつてはChromeの拡張機能で入れられたのですが、スタンドアロンアプリに変わっています。
同様の拡張機能はまだあるので、それらでも構わないと思います。

# mavenプロジェクトの作成

mavenのJerseyを使ったWebアプリをまず作成します。
こちらを参考に、コマンドで作成します。
https://docs.huihoo.com/jersey/2.13/getting-started.html#new-webapp

```console
$ mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-webapp \
-DarchetypeGroupId=org.glassfish.jersey.archetypes \
-DinteractiveMode=false \
-DgroupId=com.example \
-DartifactId=simple-service-webapp -Dpackage=com.example \
-DarchetypeVersion=2.30.1
```

`groupId`や`artifacgtId`、`package`は任意に変えてください。
Tomcatではなくて[Grizzly](https://javaee.github.io/grizzly/)で動かすので十分な場合は、[#new-from-archetype](https://docs.huihoo.com/jersey/2.13/getting-started.html#new-from-archetype)の項にあるコマンドで十分かと思います。

# IntelliJ IDEAで開く

IntelliJ IDEAにプロジェクトをインポートします。

- 起動画面で[Import Project]を選ぶ

<img width="776" alt="project-import.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/84b335e1-6ec6-8582-d00f-e8b2ca01bca0.png">

- プロジェクトフォルダを選び、[Open]をクリック
- **Maven**を選ぶ

<img width="806" alt="project-type-maven.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/7ff2efca-eb87-3b29-e9d4-744ef9acbcb4.png">

- [Finish]をクリック

プロジェクトが開きます。

# `pom.xml`を編集する

`pom.xml`を開いたら、**Enable Auto Import**をクリックしておきましょう。

## 1. javaバージョン

まず、javaバージョンが`1.7`になっているのを`11`に変更します。

```xml:pom.xml
    <build>
        <finalName>simple-webapp</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source> <!-- ここ -->
                    <target>11</target> <!-- ここ -->
                </configuration>
            </plugin>
        </plugins>
    </build>
```

## 2. 依存ライブラリの追加

### (1)Json

Jsonを使いたいので、コメントアウトされている以下のコメントを外します。

```xml:pom.xml

        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
        </dependency>
```

### (2)JUnit5

JUnit5を`<dependencies>`タグ下に追加します。

```xml:pom.mxl
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.6.0</version>
    <scope>test</scope>
</dependency>
```

※JUnitは、`juint.junit`から移動した模様です。

## 3. MyResourceクラスを眺める

**Jersey**でどのように書くのか全く見たことがない、などの場合は、`MyResouce.java`を開いて、眺めてみるとよいでしょう。

親切にコメントで何をやっているか説明してくれているので、それほど難しいことではないと思います。
ここでは、以下のことが読み取れれば問題ないかと思います。

- `@Path("myresource")`で、サーバーのルートからのパスを指定している
- `getIt()`メソッドは、
  - `@GET`なので`GET`メソッドで呼び出される
  - `@Produces(MediaType.TEXT_PLAIN)`なので、`text/plain`な文字列として提供される

`POST`は`@Post`だろうなとか、そういうことが想像できれば十分です。

あ、あとでcurlで動かす時の為に、"Got It!"の後に改行を入れておいたほうがいいかもしれません。
二つぐらい入れておいたほうが見やすいかもです。

```MyResource.java
public String getIt() {
    return "Got it!\n\n";
}
```

## 4. 動かしてみる

### (1)Tomcatから起動

Tomcatで起動する設定をし、（[前記事](https://qiita.com/kasa_le/items/6aaf17823db67c951fb0#2-configuration%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)参照)、実行します。

- [Jersey resource]のリンクをクリック

|<img width="539" alt="launch_index.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/85324764-3388-a7b3-a291-289d0171cc4c.png">|
|:--|

- 下記のようなページが表示されればOK

|<img width="590" alt="gotit.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/7c7b01e0-b0d8-9837-35bc-1f060506e613.png">|
|:--|

### (2)curlコマンドで実行
APIなので、`curl`コマンドでも叩けないとね。

urlは、Tomcat版でブラウザを開いた時のをコピーしてくると楽です。

```console
$ curl http://localhost:8080/simple_webapp_war_exploded/webapi/myresource
Got it!

$
```

改行を入れとかないと出力を見つけるのが大変です(汗)

`-i`オプションを入れると、詳細が見られます。

```console
$ curl -i http://localhost:8080/simple_webapp_war_exploded/webapi/myresource
HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 9
Date: Tue, 10 Mar 2020 06:53:25 GMT

Got it!

```

### (3) Postmanで実行

Postmanも使ってみます。
Tomcatでアプリを起動しておく必要があります。(サーバーが動いてないと当然反応はできませんので)

- Request URLに、APIへのパスを入力
  - ブラウザからコピーするのが早いです
- メソッドは`GET`を選択

|<img width="874" alt="postman.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/dcd00554-d083-9764-0d38-d3ff6e0b05e5.png">|
|:--|

- [Send]ボタンをクリック

下の欄に結果が表示されているはずです。

|<img width="956" alt="postman_get_result.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/2a6ef32f-1215-ed42-bb22-4843e1538bb6.png">|
|:--|


# サンプルAPIを作る

## 1. モデルクラス

こんなモデルクラスでデータを登録して、読み書きするAPIを作ることを考えます。

```java
public class Employee {
    private int id;
    private String firstName;


    public Employee(){}

    public Employee(int id, String firstName) {
        this.id = id;
        this.firstName = firstName;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}
```

(独り言)Kotlinなら`data class`で簡単なのになー

**追記**
空のコンストラクタは必須なので、忘れないでください。
(無いと500エラーになります。内部的には、`JSON Binding deserialization error: javax.json.bind.JsonbException: Internal error: null`という例外で落ちています。)

## 2. リポジトリクラス

データの読み書きは以下のクラスを介して行います。
本来はデータベースなどから読み出すべきですが、ここではそれは重要ではないので、とりあえずリストを持っておいて使うことにします。

```EmployeeRepository.java
public class EmployeeRepository {
    private static EmployeeRepository instance = new EmployeeRepository();

    private List<Employee> employeeList;

    private EmployeeRepository() {
        employeeList = new ArrayList<>();
    }

    public EmployeeRepository getInstance(){
        return instance;
    }

    public List<Employee> selectAll() {
        return employeeList;
    }

    public Employee select(int id){
        for (Employee employee : employeeList) {
            if(employee.getId()==id){
                return employee;
            }
        }
        throw new EmployeeNotFoundException();
    }

    public synchronized void insert(int id, String firstName){
        try{
            select(id);
        }catch (EmployeeNotFoundException e){
            // いなければ追加できる
            employeeList.add(new Employee(id, firstName));
            return;
        }
        // 同じIDが存在したら追加できない
        throw new DuplicateIdException();
    }

    public synchronized void update(int id, String firstName){
        Employee employee = select(id);
        employee.setFirstName(firstName);
    }

    public synchronized void delete(int id){
        Employee employee = select(id);
        employeeList.remove(employee);
    }

}
```

`EmployeeNotFoundException`と`DuplicateIdException`はそれぞれこんなクラスです。

```EmployeeNotFoundException.java
public class EmployeeNotFoundException extends RuntimeException {
    public EmployeeNotFoundException() {
        super("そのIDのEmployeeは見つかりません。");
    }
}
```

```DuplicateIdException.java
public class DuplicateIdException extends RuntimeException {
    public DuplicateIdException() {
        super("そのIDのEmployeeはすでに登録されています。");
    }
}
```

## 3. リソースクラス

いよいよAPIの部分です。`MyResource`の改造ではなくて、新たにリソースクラスを作っていきます。
(別に改造でもいいんですが)

### (1)初期データ

まずは簡単に初期データを登録しておいて、`GET`できるようにしましょう。
先ほどのリポジトリクラスの初期化処理に、初期データを入れておきます。

```EmployeeRepository.java
    private EmployeeRepository() {
        employeeList = new ArrayList<>();
        employeeList.add(new Employee(3, "Cupcake"));
        employeeList.add(new Employee(4, "Donuts"));
        employeeList.add(new Employee(5, "Eclair"));
        employeeList.add(new Employee(8, "Froyo"));
        employeeList.add(new Employee(9, "Gingerbread"));
    }
```

名前に「ピン」ときたあなたは、ほくそ笑んでおいてください(笑)
Eclipseで頑張っていたあの時代・・・

### (2)`GET`メソッドの作成

一番簡単な`GET`メソッド、さらにその中でも`selectAll`なAPIを作ります。

```EmployeeResource.java
@Path("/employees")
public class EmployeeResource {

    @GET
    @Path("/all")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public List<Employee> getAll(){
        return EmployeeRepository.getInstance().selectAll();
    }
}
```

- `@Path("/employees")`
  - `/employees`パスでアクセス
- `@GET`
  - `GET`メソッド
- `@Path("/all")`
  - 追加のパス。つまり、`/employees/all`でアクセスします
- `@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})`
  - JSONとXMLで返します。
  - 将来のためにXMLも返すように指定していますが、今回はまだJSONしか返しません。
- `EmployeeRepository`のシングルトンからリストを取得

### (3)`index.jsp`を編集する

ブラウザからAPIにアクセスできるよう、`index.jsp`にリンクを追加します。
お好みで見栄えを整えてください。

```index.jsp
<p><a href="webapi/employees/all">All Employee List</a>
```

### (4)リデプロイ

実行ボタンを押すと、以下のポップアップが表示されます。

<img width="302" alt="redeploy.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/90601f2a-8ab9-7d80-cb2b-498751aaf0b4.png">

- [Redeply]を選択
- [OK]をクリック
- ブラウザをリロード

これで変更が反映されるはずです。

|<img width="511" alt="reload.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/53d09674-8caf-d47b-acb9-a2422a04e07c.png">|
|:--|

### (5)動作確認

新しく作ったリンクをクリックしてみましょう。

|<img width="618" alt="get_all_json.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/d7820a66-4a75-9d0a-03dc-02b835802a92.png">|
|:--|

JSONの配列が返ってきていますね。
Postmanやcurlでも確認できます。

## 4. パスパラーメータ付きGET

### (1)リソースクラスにAPIメソッドを追加

今度は、idをパラメーターとして受け取って単独オブジェクトを返すAPIを作ってみましょう。

```EmployeeResource.java
    @GET
    @Path("/{id}")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Employee getEmployee(@PathParam("id") int id){
        return EmployeeRepository.getInstance().select(id);
    }
```

新しいのは`@Path("/{id}")`と`@PathParam()`ですね。

- `@Path("/{id}")`
  - 先ほど出てきた`@Path`と同じで、アクセスパスの指定です。{id}は任意の値が入ることを示しています。つまり、APIを使う側は、`xxxx/employee/3`などのurlでアクセスすることになります。
- `@PathParam("id")`
  - パス"{id}"に指定された値を受け取る変数を宣言しています。`xxxx/employee/3`でアクセスされると、`id=3`のEmployeeオブジェクトが渡ってきます。

### (2)`index.jsp`にリンクを追加

先ほどと同じく、`index.jsp`にリンクを追加します。

```index.jsp
<p><a href="webapi/employees/3">get id=3 employee</a>
```

### (3)実行

リデプロイして、ブラウザを再読み込みし、追加したリンクをクリックしてみてください。

|<img width="594" alt="deploy_pathparam.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/eabdd04a-7104-7b8f-37d6-47579e6ecb6b.png">|
|:--|

ちゃんと返ってきました。
Postmanやcurlでも同様に確認できます。

## 5. クエリーパラメーター検索

今度はパスパラメーターではなく、クエリーパラメーターもやってみましょう。
`/search`に対して、特定の文字(or文字列)を含むものだけ返すというのを作ってみます。

### (1)検索メソッド

まずはリポジトリクラスです。
`name`を受け取って、その文字列を含む名前のリストを作成して返します。

```EmployeeRepository.java
    public List<Employee> search(String name) {
        List<Employee> list = new ArrayList<>();
        for (Employee employee : employeeList) {
            if(employee.getFirstName().contains(name)){
                list.add(employee);
            }
        }
        if(list.size()>0) return list;
        throw new EmployeeNameNotFoundException(name);
    }
```

`EmployeeNameNotFoundException`は次のようなものです。

```EmployeeNameNotFoundException.java
public class EmployeeNameNotFoundException extends RuntimeException {
    public EmployeeNameNotFoundException(String name) {
        super("文字列{" + name + "}を含む名前のEmployeeは存在しません。");
    }
}
```

### (2)リソースクラスにAPIメソッドを追加

```EmployeeResource.java
    @GET
    @Path("/all")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public List<Employee> searchEmployee(@QueryParam("name") String name){
        return EmployeeRepository.getInstance().search(name);
    }
```

`@QueryParam`を使います。これはなんとなく予想がついたんじゃないでしょうか^^;

### (3) 実行
`index.jsp`にリンクを追加してもいいですが、もう面倒なのでPostmanを使いました(笑)
あるいは、ブラウザのURLに直打ちでもいいですね。

|<img width="963" alt="postman_query_param.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/feec446f-7738-13a9-7579-67161eb1b3c6.png">|
|:--|


# JUnitテスト

先に動作テストをしてしまっていますが、JUnitテストも書いてみます。

## 1.依存関係の設定

Jerseyのテストフレームワークを使います。ただ、JUnit5に対応していないようなので、ちょっとしたハックが必要です。

以下の依存関係を**testスコープ**に追加します。

- Jerseyのテストフレームワーク(grizzly)
  - サーバーを仮で立てるのにgrizzlyを使います。
- AssertJ
  - アサーションを書くのに使います。お好みで他でもいいですし、JUnitの標準を使っても構いません。

以下のように`pom.xml`の`<dependencies>`タグ内に追加します。
JUnitの下が良いでしょうね。

```xml:pom.xml
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.test-framework.providers/jersey-test-framework-provider-grizzly2 -->
        <dependency>
            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
            <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
            <version>2.30.1</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>
```

## 2. MyResourceのテスト
まずは簡単に、`Myrecource`クラスのテストを書いてみます。
私は`import`で悩んだのでimport文まで全て載せます。

```MyResourceTest.java
package com.example;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;

import static org.assertj.core.api.Assertions.assertThat;


class MyResourceTest extends JerseyTest {

    @Override
    protected Application configure() {
        return new ResourceConfig(MyResource.class);
    }

    @BeforeEach
    @Override
    public void setUp() throws Exception {
        super.setUp();
    }

    @AfterEach
    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @Test
    public void getIt(){
        final Response response = target("/myresource").request().get();

        String content = response.readEntity(String.class);
        assertThat(content).isEqualTo("Got it!\n\n");
    }
}
```

テストは基本的に以下のステップで書いていけば良いかと思います。

- `JerseyTest`を継承したテストクラスを作る
- `configure`メソッドをオーバーライドする
- `@BeforeEach`と`@AfterEach`メソッドを作る
  - ここがJUnit5に対応させるための苦肉の策部分になります。`JerseyTest`クラスでは、JUnit4用の`@Before`と`@After`が使われているのですが、JUnit5ではこれが呼び出されません。なので、わざわざラップしてやっています。面倒な場合は、エクステンションを作ってくださってる方などがいらっしゃるようなので、それを使うのも手かもしれません。
- テストメソッドを作る
  - `target("パス").request().get()`でリクエストを投げてレスポンスを受け取る
  - `response.readEntity(クラス名)`で戻り値を取得する
  - アサーションで結果確認

実行してみましょう。

<img width="1088" alt="run_junit.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/985fbaaf-600e-9f26-a5f4-6bb5c4d24cbe.png">

ログを見ると、ポート番号が"9998"などで動いているのがわかりますね。

通過したら、残りのテストも書いていきましょう。

## 3. EmployeeResourceのテスト

`MyResource`のテストを参考に書けると思います。

```EmployeeResourceTest.java
package com.example;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import javax.ws.rs.core.Application;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

class EmployeeResourceTest extends JerseyTest {

    @Override
    protected Application configure() {
        return new ResourceConfig(EmployeeResource.class);
    }

    @BeforeEach
    @Override
    public void setUp() throws Exception {
        super.setUp();
    }

    @AfterEach
    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @Test
    void getAll() {
        final Response response = target("/employees/all").request().get();

        List<Employee> content = response.readEntity(new GenericType<>() {});
        assertThat(content.size()).isEqualTo(5);
        assertThat(content.get(0)).isEqualToComparingFieldByField(new Employee(3, "Cupcake"));
        assertThat(content.get(1)).isEqualToComparingFieldByField(new Employee(4, "Donuts"));
        assertThat(content.get(2)).isEqualToComparingFieldByField(new Employee(5, "Eclair"));
        assertThat(content.get(3)).isEqualToComparingFieldByField(new Employee(8, "Froyo"));
        assertThat(content.get(4)).isEqualToComparingFieldByField(new Employee(9, "Gingerbread"));
    }

    @Test
    void getEmployee() {
        Employee employee = target("/employees/3").request().get(Employee.class);
        assertThat(employee).isEqualToComparingFieldByField(new Employee(3, "Cupcake"));

        employee = target("/employees/4").request().get(Employee.class);
        assertThat(employee).isEqualToComparingFieldByField(new Employee(4, "Donuts"));

        employee = target("/employees/5").request().get(Employee.class);
        assertThat(employee).isEqualToComparingFieldByField(new Employee(5, "Eclair"));

        employee = target("/employees/8").request().get(Employee.class);
        assertThat(employee).isEqualToComparingFieldByField(new Employee(8, "Froyo"));

        employee = target("/employees/9").request().get(Employee.class);
        assertThat(employee).isEqualToComparingFieldByField(new Employee(9, "Gingerbread"));
    }

    @Test
    void search(){
        final List<Employee> content = target("/employees/search")
                .queryParam("name", "a")
                .request()
                .get(new GenericType<>() {});

       assertThat(content.size()).isEqualTo(3);
        assertThat(content.get(0)).isEqualToComparingFieldByField(new Employee(3, "Cupcake"));
        assertThat(content.get(1)).isEqualToComparingFieldByField(new Employee(5, "Eclair"));
        assertThat(content.get(2)).isEqualToComparingFieldByField(new Employee(9, "Gingerbread"));
    }
}
```

`getEmployee`のテストで、

```java
Employee employee = target("/employees/3").request().get(Employee.class);
```

としています。`get()`に型を指定すると、`readEntity`をその型で処理した値を直接受け取れます。

また、`search`のテストでは、`queryParam`を使って、クエリーパラメーターを指定しています。

# 感想

GET系は、たぶん、簡単です。まあ色々ハマりはしましたが。JerseyテストフレームワークがJUnit5に対応していないせいでテストが動かないとか。。。対処法はごく簡単なのに、何時間悩んだことか・・・(泣)

でも、XMLを返そうとしたり、POSTをやろうとしたらもっとハマったので、それを次回にします。
あと、例外系も次回やります。
今は存在しないidを指定して検索すると、500エラーになりますが、これを変えていきます。

# 参考ページ

いっぱい見たなあ・・・

- REST API Tutorial Create REST APIs with JAX-RS 2.0  
  https://restfulapi.net/create-rest-apis-with-jax-rs-2-0/
- Starting out with Jersey & Apache Tomcat using IntelliJ  
  https://medium.com/@jamsesso/starting-out-with-jersey-apache-tomcat-using-intellij-6338d93ffd40
- RESTサービスを触る際の必須ツールPostmanを使ってみました  
  https://www.xlsoft.com/jp/blog/blog/2017/06/23/post-1638/
- JerseyとSpringでのREST API  
  https://www.codeflow.site/ja/article/jersey-rest-api-with-spring
- Github - macoshita/jersey-sample  
  https://github.com/macoshita/jersey-sample
- Jersey公式 - Chapter 2. Modules and dependencies  
  https://docs.huihoo.com/jersey/2.13/modules-and-dependencies.html
- Jersey & Grizzly で始める JAX-RS 入門 〜STEP1〜  
  https://blog1.mammb.com/entry/2016/03/19/235012
- JAX-RS（Jersey）を使って RESTful API を実装してみよう【導入編】  
  https://www.indetail.co.jp/blog/170228/
- JAX-RS（Jersey）を使って RESTful API を実装してみよう【応用編】  
  https://www.indetail.co.jp/blog/170309/
- Exploring the Jersey Test Framework  
https://www.baeldung.com/jersey-test
- JerseyTest is not compatible with JUnit 5  
https://github.com/jersey/jersey/issues/3662
- JAX-RS - JerseyTest Examples  
https://www.logicbig.com/how-to/code-snippets/jcode-jax-rs-jerseytest.html
- jerseyの@QueryParamと@PathParamはURLデコードを自動でする  
https://qiita.com/n_slender/items/41dfabc6ab8c29364cab
- Set Query Parameters on a Jersey Test Call  
https://stackoverflow.com/questions/24770027/set-query-parameters-on-a-jersey-test-call