0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」 - FollowingTestの問題点と、その改良

Posted at

前提となるテストの内容

Railsチュートリアル本文、リスト 14.40記載のテストコードです。

test/integration/following_test.rb
require 'test_helper'

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

  # ...略

  test "should follow a user the standard way" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, params: { followed_id: @other.id }
    end
  end

  test "should follow a user with Ajax" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, xhr: true, params: { followed_id: @other.id }
    end
  end

  test "should unfollow a user the standard way" do
    @user.follow(@other)
    relationship = @user.active_relationships.find_by(followed_id: @other.id)
    assert_difference '@user.following.count', -1 do
      delete relationship_path(relationship)
    end
  end

  test "should unfollow a user with Ajax" do
    @user.follow(@other)
    relationship = @user.active_relationships.find_by(followed_id: @other.id)
    assert_difference '@user.following.count', -1 do
      delete relationship_path(relationship), xhr: true
    end
  end
end

RelationshipsController#createにおいて、format.js以下の行が欠落していても上記テストが落ちない理由

前提として、RelationshipsController#createの実装は以下のようになっています。

app/controllers/relationships_controller.rb#create
def create
  @user = User.find(params[:followed_id])
  current_user.follow(@user)
  respond_to do |format|
    format.html { redirect_to @user }
    format.js
  end
end

createメソッド内2行目の時点で、followメソッドの処理に問題がなければ、@user.following.countの数は1増えます。

ここで、format.js以下の行が欠落している場合、RelationshipsController#createは、「Ajaxによる呼び出しであっても、非Ajax(通常のリダイレクトを伴う呼び出し)として以降の処理を行います。となると、「format.js以下の行が欠落していても、format.html以下の行さえあれば、とりあえず当該テストは通ってしまう」という動作になるのです。

現状の実装で、テスト「should follow a user with Ajax」はどんな場合に落ちるのか

Railsチュートリアル本文のリスト 14.40において、テスト「should follow a user with Ajax」のソースコードは、以下のようになっています。

test "should follow a user with Ajax" do
  assert_difference '@user.following.count', 1 do
    post relationships_path, xhr: true, params: { followed_id: @other.id }
  end
end

RelationshipsController#createにおいて、format.js以下の行が欠落していてもこのテストが落ちない」というのであれば、どのような場合にこのテストは落ちるのでしょうか。

一つの答えは、「app/views/relationshipsディレクトリに、create.js.erbがなくてcreate.css.erbだけが存在する場合」です。この場合、当該テストは以下のようなエラーで落ちることになります。

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

ERROR["test_should_follow_a_user_with_Ajax", FollowingTest, 4.884168800000225]
 test_should_follow_a_user_with_Ajax#FollowingTest (4.88s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: RelationshipsController#create is missing a template for this request format and variant.
        
        request.formats: ["text/javascript", "text/html", "application/xml", "*/*"]
        request.variant: []
            test/integration/following_test.rb:37:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:36:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.96005s
6 tests, 13 assertions, 0 failures, 1 errors, 0 skips

ActionController::UnknownFormatエラーでテストが落ちていますね。

test/integration/following_test.rbの、より望ましいであろう実装

ビューによりレンダリングされる内容に対してテストを行うのであれば、以下のような実装がより望ましいかと思います。

test/integration/following_test.rb
  require 'test_helper'

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

...略

    test "should follow a user the standard way" do
      assert_difference '@user.following.count', 1 do
        post relationships_path, params: { followed_id: @other.id }
      end
+     assert_redirected_to @other
    end

    test "should follow a user with Ajax" do
      assert_difference '@user.following.count', 1 do
        post relationships_path, xhr: true, params: { followed_id: @other.id }
      end
+     assert_match 'Unfollow', @response.body
    end

    test "should unfollow a user the standard way" do
      @user.follow(@other)
      relationship = @user.active_relationships.find_by(followed_id: @other.id)
      assert_difference '@user.following.count', -1 do
        delete relationship_path(relationship)
      end
+     assert_redirected_to @other
    end

    test "should unfollow a user with Ajax" do
      @user.follow(@other)
      relationship = @user.active_relationships.find_by(followed_id: @other.id)
      assert_difference '@user.following.count', -1 do
        delete relationship_path(relationship), xhr: true
      end
+     assert_match 'Follow', @response.body
    end
  end

上記テストの追加内容は以下の通りです。

  • 非Ajaxの場合、@otherの内容たるユーザーのプロフィールページにリダイレクトされることをテストする
  • Ajaxの場合、以下の処理が行われることをテストする
    • Followボタンが押された場合、「Unfollow」というキャプションを持つボタンを生成するJavaScriptコードが返されること
    • Unfollowボタンが押された場合、「Follow」というキャプションを持つボタンを生成するJavaScriptコードが返されること

「より望ましいであろう実装」のテスト結果

「should follow a user the standard way」のテストが落ちるパターン

以下のように、「RelationshipsController#createにおいて、format.html以下の行が欠落している場合」、「should follow a user the standard way」のテストが落ちることが期待されます。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
-     format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

実際にテスト「should follow a user the standard way」を実行すると、結果は以下のようになります。

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

ERROR["test_should_follow_a_user_the_standard_way", FollowingTest, 4.109552399999302]
 test_should_follow_a_user_the_standard_way#FollowingTest (4.11s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: ActionController::UnknownFormat
            app/controllers/relationships_controller.rb:7:in `create'
            test/integration/following_test.rb:30:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:29:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.22645s
6 tests, 19 assertions, 0 failures, 1 errors, 0 skips

「should follow a user with Ajax」のテストが落ちるパターン

以下のように、「RelationshipsController#createにおいて、format.js以下の行が欠落している」という場合、「should follow a user with Ajax」のテストが落ちることが期待されます。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
-     format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

実際にテスト「should follow a user with Ajax」を実行すると、結果は以下のようになります。

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

 FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.8547959999996237]
 test_should_follow_a_user_with_Ajax#FollowingTest (1.85s)
        Expected /Unfollow/ to match "Turbolinks.clearCache()\nTurbolinks.visit(\"http://www.example.com/users/391532587\", {\"action\":\"replace\"})".
        test/integration/following_test.rb:39:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.41998s
6 tests, 22 assertions, 1 failures, 0 errors, 0 skips

「should unfollow a user the standard way」のテストが落ちるパターン

以下のように、「RelationshipsController#destroyにおいて、format.html以下の行が欠落している場合」、「should follow a user the standard way」のテストが落ちることが期待されます。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
-     format.html { redirect_to @user }
      format.js
    end
  end
end

実際にテスト「should unfollow a user the standard way」を実行すると、結果は以下のようになります。

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

ERROR["test_should_unfollow_a_user_the_standard_way", FollowingTest, 3.8890324999993027]
 test_should_unfollow_a_user_the_standard_way#FollowingTest (3.89s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: ActionController::UnknownFormat
            app/controllers/relationships_controller.rb:16:in `destroy'
            test/integration/following_test.rb:46:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:45:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.18608s
6 tests, 19 assertions, 0 failures, 1 errors, 0 skips

「should unfollow a user with Ajax」のテストが落ちるパターン

以下のように、「RelationshipsController#destroyにおいて、format.js以下の行が欠落している場合」、「should unfollow a user with Ajax」のテストが落ちることが期待されます。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
-     format.js
    end
  end
end

実際にテスト「should unfollow a user with Ajax」を実行すると、結果は以下のようになります。

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

 FAIL["test_should_unfollow_a_user_with_Ajax", FollowingTest, 1.7373090000000957]
 test_should_unfollow_a_user_with_Ajax#FollowingTest (1.74s)
        Expected /Follow/ to match "Turbolinks.clearCache()\nTurbolinks.visit(\"http://www.example.com/users/391532587\", {\"action\":\"replace\"})".
        test/integration/following_test.rb:57:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.29870s
6 tests, 22 assertions, 1 failures, 0 errors, 0 skips

ソースコードの実装に問題がない場合

最後に、app/controllers/relationships_controller.rbの内容が以下の通り問題なく記述されている場合、テストの結果はどうなるでしょうか。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

実際にテストを実行してみます。

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

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.64541s
6 tests, 22 assertions, 0 failures, 0 errors, 0 skips

無事テストが通りました。というわけで、テストの内容に問題はないようです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?