Testの書き方 LiveView編

- [Testの書き方 LiveView編](#testの書き方-liveview編)
  - [モジュールのインポート](#モジュールのインポート)
  - [セットアップ関数の定義](#セットアップ関数の定義)
    - [データが異なる場合のセットアップ関数](#データが異なる場合のセットアップ関数)
  - [Testを実行する](#testを実行する)
    - [`** (KeyError) key :myself not found`](#-keyerror-key-myself-not-found)
  - [結論](#結論)
  - [おまけ](#おまけ)
    - [テスト実行後の処理](#テスト実行後の処理)
    - [要素をクリックした後の表示をテスト](#要素をクリックした後の表示をテスト)
    - [`navigate`とパッチの処理をテスト](#navigateとパッチの処理をテスト)
    - [テスト名](#テスト名)

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html

# Testの書き方 LiveView編

LiveViewのテストモジュールは、以下の手順で定義することができる。

* テストモジュールに必要なモジュールをインポートする。
* セットアップ関数を定義する。
* テストを記述する。

## モジュールのインポート

LiveViewのテストモジュールを定義するためには、最低限以下の記述を必要とする。

* `use 省略Web.ConnCase`
* `import Phoenix.LiveViewTest`

これらは、Phoenixの接続のテストとLiveViewのテストを行えるようにするものである。

## セットアップ関数の定義

LiveViewではライフサイクルにデータを追加することができる(**socket.assigns**)。

このデータ込みのテストを作成するためには、
テストを初期化する<font color="red">**セットアップ関数**</font>を定義する必要がある。

セットアップ関数は次のように定義することができる。

```elixir
# セットアップ関数を定義
defp init_data(_) do
  %{name: "Taro"}
end
```

セットアップ関数は次のように、テストに指定することができる。

```elixir
describe "Test HomeLive.Index" do
  # describe別にsetup関数を指定することができる
  setup [:init_data]

  # セットアップ関数の戻り値のマップにあるものが指定できる
  test "home page", %{conn: conn, name: name} do

  end
end
```

また<font color="red">**`setup_all`マクロ**</font>を使用することで、モジュール内の**すべてのテストの初期化処理を行う**ことができる。

次のように記述する。

```elixir
setup_all do
  # 初期化処理
end
```

これらの実行順は次のようになる。

1. `setup_all`マクロ
2. セットアップ関数

よって<font color="red">テスト全体に適用したい初期化処理</font>は<font color="black">**`setup_all`マクロ**</font>に、<font color="red">テストごとの初期化処理</font>を**セットアップ関数**で行うと良い。


### データが異なる場合のセットアップ関数

データの値が特定の状態のときのLiveViewをテストしたい場合、
複数セットアップ関数を用意するとよい。

```elixir
defp when_name(_) do
  %{name: "Taro", age: nil, from: nil}
end
defp when_age(_) do
  %{name: "Taro", age: 34, from: nil}
end
defp when_from(_) do
  %{name: "Taro", age: 34, from: "north pole"}
end
```

## Testを実行する

LiveViewをテストする際には、LiveViewのプロセスを生成する必要がある。
次のように記述することで、LiveViewのプロセスを生成することができる。

```elixir
{:ok, live_view, html} = live(conn, ~p"ルート")
```

出力される`live_view`は<font color="red">**LiveViewの状態**</font>であり、`html`は<font color="red">レンダリングされたHTMLである</font>

LiveComponentの表示は次のようにテストすることができる。

```elixir
# 出力されるHTMLからテスト
assert html =~ "テキスト"
# 特定のLiveComponentからの出力に絞ってテスト
assert render_component(LiveComponentモジュール, データatom: データ) =~ "テキスト"
```

### `** (KeyError) key :myself not found`

LiveConponent内で<font>**イベントの送信先をLiveComponent自身(`phx-target={@myself}`)**</font>としている場合、次のエラーが表示される。

```
** (KeyError) key :myself not found 以下省略
```

このような場合のTestでは、render_componentで<font color="red">**`id: "LiveComponentに指定したid"`**</font>を渡すことで解決する。

```elixir
assert render_component(LiveComponentモジュール, id: "LiveComponentに指定したid", データatom: データ) =~ "テキスト"
```

## 結論

LiveViewのテストを定義する場合、概ね次の手順に沿って記述する。

* LiveViewのテストモジュールをインポートする(`ConnCase`、`Phoenix.LiveViewTest`)
* `setup_all`マクロですべてのテストで共通の初期化を記述する。
* セットアップ関数を定義して、各describeにまとめられたテストで実行させる。(`setup [:セットアップ関数]`)
* test内でLiveViewプロセスを生成する。(`live(conn, ~p"ルート")`)
* `assert`で比較を行う。

LiveViewのテストは、処理ごとに細かく`describe`で分け、結果ごとに`test`を定義したほうがよいと思った。

## おまけ

### テスト実行後の処理

セットアップ関数や`setup_all`マクロ、テストでの実行後の処理は、
<font color="red">**`on_exit`コールバック**</font>で記述することができる。

```elixir
on_exit(fn ->
  # テスト後の処理
end)
```

例として`setup_all`マクロで、`on_exit`を記述すると次のようになる。

```elixir
setup_all do
  # テスト実行前にディレクトリとファイルを生成した
  :ok = File.mkdir("TEST_DIR")
  :ok = File.touch("TEST/TEXT.txt")


  on_exit(fn ->
    {:ok, ["TEST_DIR", "TEST_DIR/TEXT.txt"]} = FILE.rm_ref("TEST_DIR")
  end)
end
```

### 要素をクリックした後の表示をテスト

要素をクリックしたときの動作のテストを記述する場合、<font color="red">**`render_click(要素)`**</font>を使用する。

またこの際、指定した要素がイベント処理によって状態(socket内のデータ)を更新する場合、`render(状態)`を実行すると
**状態の更新後の表示をテストする**ことができる。

```elixir
{:ok, live_view, _html} = live(conn, ~p"/")

live_view
|> element("li",  "イベント名もしくはphx-value-*の値")
|> render_click() =~ "内容"

# 状態の更新後をテスト
assert render(live_view) =~ "内容"
```

要素を指定する<font color="red">**`element/2`**</font>の引数は次のものを指定する。

* 第一引数 - タグの種類を指定する。(`button`、`li`など)
* 第二引数 - `phx-click`のイベント名や`phx-value-*`で指定した値などを指定する。

クリックした要素に`phx-click`が記述されていればイベントが送信され、要素が`<.link pacth={~p"ルート"}>`であればパッチ処理が行われる。

つまりLiveViewのページでクリックをしたときと同じ処理が行われる。

次のものは`li`要素をクリックすることで、
LiveComponentの表示が更新されたことをテストするものである。

```elixir
# このセットアップ関数は%{selected: nil}を返す
setup [:when_not_selected_exists]

test "Click list", %{conn: conn, selected: selected} do
  {:ok, live_view, _html} = live(conn, ~p"/")

  # <li phx-value-name="TEST" phx-click="select_li"> という要素があるものとする
  
  # "select_li"イベントは、phx-value-*で指定した値%{"name" => name}を受け取り
  # selectedの値をname(つまり"TEST")に更新するものとする

  live_view
  # すべてのliの内、"TEST"とphx-valueのnameと一致するもの
  |> element("li", "TEST")
  # この時点でtest時に指定したselectedの値は、イベント処理によって
  # nilから"TEST"となっている
  # LiveViewの要素(LiveComponent込み)内に"test.pub"というテキストが存在するかテストすることができた。
  |> render_click() =~ "test.pub" 
end
```

### `navigate`とパッチの処理をテスト

要素をクリック後、navigate処理が行われたをしたことをテストする場合

```elixir
assert_redirected(live_view, ~p"/new")
```

要素をクリック後、パッチ処理が行われたことをテストする場合

```elixir
assert_patched(live_view, ~p"/import_form")
```

### テスト名

定義例

* `Test that 要素 be 動詞ed.` - 特定の要素をテストする場合(`Test that selected directory file are listed.`等)
* `Test 対象 when 条件.` - 条件を満たすとき対象をテストする場合(`Test page when selected is nil.`)