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


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

実は、Railsチュートリアル本文のリスト 14.29に記述されているテストには、1つの不具合があります。

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

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ブラウザにおける表示は以下のようになります。

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

しかしながら、Railsチュートリアル本文のリスト 14.29に記述されているテストの場合、この状態でもテストは成功してしまうのです。

# 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に記述されているテストは成功してしまいます。

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つ1」存在します。そのため、「リンクが1つ以上」というテストの実装では、「サイドバー」「FollowingまたはFollowersの一覧」いずれか片方の欠落ではテストをすり抜けてしまうのです。

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

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

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

 
  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を対象に、改めて以下のコードのテストを行ってみます。

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つ以上存在すること」に対する正しいテストが書けているようです。


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