Phoenixでインテグレーションテストをする


## やりたいこと/やること

Elixir/PhoenixでSessionを使用した画面遷移を伴うインテグレーションテストをしたい
PhoenixデフォルトではSessionを扱ったテストを書くのが少し面倒なようなので、今回はインテグレーションテスト用のライブラリでPhantomJS経由でテストをします


ここらへん公式でドキュメントもっと充実するかデフォルトで簡単にインテグレーションテスト出来るといいのになー

## 環境

- [Phoenix](http://www.phoenixframework.org/) `1.1.4`
- Elixir `1.2.5`
- hound `1.0.0`
- PhantomJS `2.1.1`
- Docker `1.11.1`

## 手順

- ElixirのIntegration Testライブラリの[hound](https://github.com/HashNuke/hound)をmixの依存に追加

`mix.exs`

```elixir
{:hound, "~> 1.0"}
```

依存を更新

```bash
mix deps.get
```

- ローカルにPhantomJSを入れるのが面倒なのでリモートサーバとしてPhantomJSをDockerで起動する

```bash
docker run -itd -p 8910:8910 shufo/phantomjs --webdriver 8910
```

docker-composeを使っているならサービスを`docker-compose.yml`に追加する

```yaml
  phantomjs:
    image: shufo/phantomjs
    container_name: phantomjs
    hostname: phantomjs
    command: "--webdriver 8910"
```

- `hound`の設定

`config/config.ex`

```elixir
config :hound, driver: "phantomjs", host: "phantomjs", port: 8910
```

補足: `host`は起動したphantomjsのホスト名。docker-composeで起動した場合は自動的にservice名で解決出来るので`phantomjs`でok。docker-composeを使わない場合はDockerデーモンを起動しているマシンのホスト名か`docker inspect`等でコンテナのIPを確認。

`test_helpers.exs`

```elixir
Application.ensure_all_started(:hound)
ExUnit.start
```

`ExUnit.start`の前に`Application.ensure_all_started(:hound)`を追加

`config/test.exs`

```diff
config :example, Example.Endpoint,
  http: [port: 4001],
-  server: false
+  server: true
```

MIX_ENV=test時のEndpointのconfigを`server: true`に変更


- Testを書く

`test/controllers/auth_controller_test.exs`

```elixir
defmodule Example.AuthControllerTest do
  use Example.ConnCase
  import Example.Router.Helpers
  use Hound.Helpers

  # Start hound session and destroy when tests are run
  hound_session

  @moduletag :auth

  setup do
    # ユーザ作成処理等
    # ～中略～

    navigate_to("/auth/login")
    assert page_source =~ "ログイン"

    username = "foobar"
    find_element(:name, "username")
    |> fill_field(username)

    find_element(:name, "password")
    |> fill_field("123456")

    find_element(:tag, "button")
    |> click

    assert page_source =~ "ようこそ"
    {:ok, username: username}
  end

  test "user can logout", %{username: username} do
    # click logout
    find_element(:id, "button_get_logout")
    |> click
    # Assert redirected to login page
    assert page_source =~ "ログイン"
    assert current_path == auth_path(conn, :get_login)
    # Assert logouted user can't access to private page
    navigate_to("/mypage")
    assert current_path == auth_path(conn, :get_login)
  end
end
```

補足: `setup`内で共通処理をしてログイン。HoundのHelperの`find_element/2`で要素を選んで値を入れたり出来る。


- Test実行

```bash
mix test --only auth
```

`@moduletag`でテストの実行範囲を絞れる

これで一通りインテグレーションテストが出来た。

`find_element/2`で指定できるelementの指定の方法は他にも`:name`, `:tag`, `:id`, `:class`, `:css`, `:link_text`等がある。
`fill_element/2`で選択した要素にinput valueを入れて`click/1`か`submit/1`でフォームをsubmitして`current_path/0`か`current_url/0`で遷移先のパスが正しいか確認。あと`page_source/0`でページに期待する文字列が含まれるかチェック、辺りが出来るので基本的なところは十分だと思うけど足りない場合は [Hexのdocument](https://hexdocs.pm/hound/readme.html) を参照してください。

### 追記:

少しやり方が特殊なテストでハマったので追記

- ファイルアップロード

`input_into_field/2`に対してファイルのフルパスを入力する

```elixir
find_element(:name, "image")
|> input_into_field("#{__DIR__}/../../fixtures/example.jpg")
```

ファイルはphantomjsが実行されている環境のローカルファイルシステムを参照するのでDockerで起動している場合はコンテナに必要なファイルをマウントしておく

例)

```bash
docker run -itd -p 8910:8910 -v $(pwd):/app shufo/phantomjs --webdriver 8910
```

```diff
  phantomjs:
    image: shufo/phantomjs
    container_name: phantomjs
    hostname: phantomjs
    command: "--webdriver 8910"
+    volumes:
+      - ".:/app"
```

カレントディレクトリを`/app`にマウントするので`input_into_field`では`/app/test/fixtures/example.jpg`などで参照出来る

- セレクトボックスの選択

```elixir
find_element(:css, "#element_id option[value='value']")
|> click
```