Railsチュートリアル 第14章 ユーザーをフォローする - followingアクションおよびfollowersアクションの統合テストに存在する不具合の修正

# `following`アクションおよび`followers`アクションの統合テストにおける、Railsチュートリアル本文記載のテストの不具合

実は、Railsチュートリアル本文の[リスト 14.29](https://railstutorial.jp/chapters/following_users?version=5.1#code-following_tests)に記述されているテストには、1つの不具合があります。

例えば、`app/views/users/show_follow.html.erb`に以下の欠落がある場合を考えてみましょう。

```diff:app/views/users/show_follow.html.erb
  <% provide(:title, @title) %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        ...略
      </section>
      <section class="stats">
        <%= render'shared/stats' %>
        <% if @users.any? %>
          <div class="user_avatars">
            <% @users.each do |user|%>
              <%= link_to gravatar_for(user, size: 30), user %>
            <% end %>
          </div>
        <% end %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3><%= @title %></h3>
      <% if @users.any? %>
        <ul class="users follow">
-         <%= render @users %>
        </ul>
        <%= will_paginate %>
      <% end %>
    </div>
  </div>
```

の場合、例えば users/1/following のWebブラウザにおける表示は以下のようになります。

<img width="1161" alt="スクリーンショット 2020-02-06 8.09.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/a710eb96-790d-9680-2d08-072cd5e94f99.png">

ページ右側にフォローしているユーザー一覧が描画されていません。明らかに意図した表示内容ではないですね。

しかしながら、Railsチュートリアル本文の[リスト 14.29](https://railstutorial.jp/chapters/following_users?version=5.1#code-following_tests)に記述されているテストの場合、この状態でもテストは成功してしまうのです。

```
# rails test test/integration/following_test.rb
Running via Spring preloader in process 1248
Started with run options --seed 41256

  2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.66436s
2 tests, 10 assertions, 0 failures, 0 errors, 0 skips
```

同様に、`app/views/users/show_follow.html.erb`に以下の欠落がある場合でも、Railsチュートリアル本文の[リスト 14.29](https://railstutorial.jp/chapters/following_users?version=5.1#code-following_tests)に記述されているテストは成功してしまいます。

```diff:app/views/users/show_follow.html.erb
  <% provide(:title, @title) %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        ...略
      </section>
      <section class="stats">
        <%= render'shared/stats' %>
        <% if @users.any? %>
          <div class="user_avatars">
            <% @users.each do |user|%>
              <%= link_to gravatar_for(user, size: 30), user %>
            <% end %>
          </div>
        <% end %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3><%= @title %></h3>
      <% if @users.any? %>
        <ul class="users follow">
-         <%= render @users %>
        </ul>
        <%= will_paginate %>
      <% end %>
    </div>
  </div>
```

不具合の原因は、テストの実装が「ユーザーのプロフィールページへのリンクが**1つ以上**あればOK」という内容になっているためです。単一ユーザーのプロフィールページへのリンクは、「サイドバーのアイコンで1つ、FollowingまたはFollowersの一覧で1つ〜2つ[^リンクが2つ存在する場合]」存在します。そのため、「リンクが1つ以上」というテストの実装では、「サイドバー」「FollowingまたはFollowersの一覧」いずれか片方の欠落ではテストをすり抜けてしまうのです。

[^リンクが2つ存在する場合]: ログインユーザーがAdmin属性である場合、ユーザーを削除するためのリンクも、リンク先のURLは当該ユーザーのプロフィールページへのリンクのURLと同じになります。違うのは、発行されるアクションが`GET`であるか`DELETE`であるかです。

# `following`アクションおよび`followers`アクションの統合テストの不具合を修正する

ユーザーのプロフィールページへのリンクの存在によって「サイドバー」「FollowingまたはFollowersの一覧」両方が正しく描画されていることを確認するためには、「ユーザーのプロフィールページへのリンクが**2つ以上**存在すること」をテストする必要があります。`assert_select`で「要素が2つ以上存在すること」をテストするためには、オプションハッシュに`minimum: 2`という設定を与えればOKです。

上記を踏まえ、`test/integration/following_test.rb`の修正内容は以下のようになります。

```diff:　
  require 'test_helper'

  class FollowingTest < ActionDispatch::IntegrationTest
    def setup
      @user = users(:rhakurei)
      log_in_as(@user)
    end

    test "following page" do
      get following_user_path(@user)
      assert_not @user.following.empty?
      assert_match @user.following.count.to_s, response.body
      @user.following.each do |user|
-       assert_select "a[href=?]", user_path(user)
+       assert_select "a[href=?]", user_path(user), minimum: 2
      end
    end

    test "followers page" do
      get followers_user_path(@user)
      assert_not @user.followers.empty?
      assert_match @user.followers.count.to_s, response.body
      @user.followers.each do |user|
-       assert_select "a[href=?]", user_path(user)
+       assert_select "a[href=?]", user_path(user), minimum: 2
      end
    end
  end
```

# 上記修正は本当に正しいのか

上記修正を行った`test/integration/following_test.rb`を対象に、改めて以下のコードのテストを行ってみます。

```diff:app/views/users/show_follow.html.erb
  <% provide(:title, @title) %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        ...略
      </section>
      <section class="stats">
        <%= render'shared/stats' %>
        <% if @users.any? %>
          <div class="user_avatars">
            <% @users.each do |user|%>
              <%= link_to gravatar_for(user, size: 30), user %>
            <% end %>
          </div>
        <% end %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3><%= @title %></h3>
      <% if @users.any? %>
        <ul class="users follow">
-         <%= render @users %>
        </ul>
        <%= will_paginate %>
      <% end %>
    </div>
  </div>
```

結果は以下のようになります。

```
# rails test test/integration/following_test.rb
Running via Spring preloader in process 1235
Started with run options --seed 28029

 FAIL["test_followers_page", FollowingTest, 2.6110920999926748]
 test_followers_page#FollowingTest (2.61s)
        Expected at least 2 elements matching "a[href="/users/919532091"]", found 1..
        Expected 1 to be >= 2.
        test/integration/following_test.rb:23:in `block (2 levels) in <class:FollowingTest>'
        test/integration/following_test.rb:22:in `block in <class:FollowingTest>'

 FAIL["test_following_page", FollowingTest, 2.699444500001846]
 test_following_page#FollowingTest (2.70s)
        Expected at least 2 elements matching "a[href="/users/314048677"]", found 1..
        Expected 1 to be >= 2.
        test/integration/following_test.rb:14:in `block (2 levels) in <class:FollowingTest>'
        test/integration/following_test.rb:13:in `block in <class:FollowingTest>'

  2/2: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.71399s
2 tests, 8 assertions, 2 failures, 0 errors, 0 skips
```

`Expected 1 to be >= 2.`というのがポイントですね。「ユーザーのプロフィールページへのリンクが2つ以上存在すること」に対する正しいテストが書けているようです。