Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」

# 1. [リスト 14.36](https://railstutorial.jp/chapters/following_users?version=5.1#code-relationships_controller_ajax)の`respond_to`ブロック内の各行を順にコメントアウトしていき、テストが正しくエラーを検知できるかどうか確認してみましょう。実際、どのテストケースが落ちたでしょうか?

## `RelationshipsController#create`において、`format.html`以下の行が欠落している場合

`RelationshipsController#create`に以下の変更を加えた場合の動作から見てみましょう。

```diff: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
```

上記変更を適用した上で、`test/integration/following_test.rb`を対象としてテストを実行した結果は以下です。

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

ERROR["test_should_follow_a_user_the_standard_way", FollowingTest, 3.4918121000082465]
 test_should_follow_a_user_the_standard_way#FollowingTest (3.49s)
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:03, Time: 00:00:03

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

エラー内容が`ActionController::UnknownFormat`であることがポイントです。上記エラーログでは端折られていますが、今回発生した`ActionController::UnknownFormat`エラーのより詳細なエラーメッセージは以下のようになります。

```
ActionController::UnknownFormat: RelationshipsController#create is missing a template for this request format and variant.
        
        request.formats: ["text/html"]
        request.variant: []
```

「RelationshipsController#create is missing a template（略）」とありますね。`create`コントローラーに対するビューのテンプレート、ファイル名としては`app/views/relationships/create.html.erb`が存在しないためのエラーです。

## `RelationshipsController#create`において、`format.js`以下の行が欠落している場合

続いて、`RelationshipsController#create`に以下の変更を加えた場合の動作です。

```diff: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
```

上記変更を適用した上で、`test/integration/following_test.rb`を対象としてテストを実行した結果は以下です。

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

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

Finished in 3.26699s
6 tests, 14 assertions, 0 failures, 0 errors, 0 skips
```

テストは正常に完了します。テスト「should follow a user with Ajax」が落ちることを期待していたのですが、違いますね。

## `RelationshipsController#destroy`において、`format.html`以下の行が欠落している場合

今度は`RelationshipsController#destroy`に以下の変更を加えた場合の動作です。

```diff:app/controllers/relationships_controller.rb#destroy
  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
```

上記変更を適用した上で、`test/integration/following_test.rb`を対象としてテストを実行した結果は以下です。

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

ERROR["test_should_unfollow_a_user_the_standard_way", FollowingTest, 4.025347000000693]
 test_should_unfollow_a_user_the_standard_way#FollowingTest (4.03s)
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.16709s
6 tests, 13 assertions, 0 failures, 1 errors, 0 skips
```

`ActionController::UnknownFormat`エラーでテストが落ちています。「`RelationshipsController#create`において、`format.html`以下の行が欠落している場合」と同様の挙動ですね。

## `RelationshipsController#destroy`において、`format.js`以下の行が欠落している場合

最後は`RelationshipsController#destroy`に以下の変更を加えた場合の動作です。

```diff:app/controllers/relationships_controller.rb#destroy
  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
```

上記変更を適用した上で、`test/integration/following_test.rb`を対象としてテストを実行した結果は以下です。

```
# rails test test/integration/following_test.rb
NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed on or after 2019-12-01.
Gem::Specification#rubyforge_project= called from /usr/local/bundle/specifications/i18n-0.9.5.gemspec:17.
NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed on or after 2019-12-01.
Gem::Specification#rubyforge_project= called from /usr/local/bundle/specifications/i18n-0.9.5.gemspec:17.
Running via Spring preloader in process 573
Started with run options --seed 63825

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

Finished in 4.95860s
6 tests, 14 assertions, 0 failures, 0 errors, 0 skips
```

テストは正常に完了します。

# 発展 - 

# 2. [リスト 14.40](https://railstutorial.jp/chapters/following_users?version=5.1#code-follow_button_tests)の`xhr: true`がある行のうち、片方のみを削除するとどういった結果になるでしょうか? このとき発生する問題の原因と、なぜ先ほどの演習で確認したテストがこの問題を検知できたのか考えてみてください。

`test/integration/following_test.rb`の内容を以下のように改変した場合、テストの結果はどうなるでしょうか。「テスト『should follow a user with Ajax』において、`xhr: true`がある行を削除する」という改変です。

```diff: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
```

上記テストの実行結果は以下のようになります。

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

 FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.9365238999998837]
 test_should_follow_a_user_with_Ajax#FollowingTest (1.94s)
        "@user.following.count" didn't change by 1.
        Expected: 3
          Actual: 2
        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.76945s
6 tests, 14 assertions, 1 failures, 0 errors, 0 skips
```

上記テストでは、テスト「should follow a user with Ajax」において、`POST`リクエスト自体が発行されていません。`POST`リクエストが発行されないことには、`RelationshipsController#create`メソッドも実行されません。当該`RelationshipsController#create`の内容は以下の通りです。

```ruby:RelationshipsController#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
```

RDB上のデータにフォローを反映する処理は、上記コードのうち`current_user.follow(@user)`となります。` current_user.follow(@user)`が実行されなければ、`@user.following.count`の値も変化することはありません。結果、「`@user.following.count`の値が想定と違う」という理由でテストが落ちることになります。

逆に、「テスト『should unfollow a user with Ajax』において、`xhr: true`がある行を削除する」という改変を加えた場合（下記ソース）も、同様の形でテストが落ちます。

```diff: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
```

上記テストの実行結果は以下のようになります。

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

 FAIL["test_should_unfollow_a_user_the_standard_way", FollowingTest, 4.007498499999201]
 test_should_unfollow_a_user_the_standard_way#FollowingTest (4.01s)
        "@user.following.count" didn't change by -1.
        Expected: 2
          Actual: 3
        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.10546s
6 tests, 14 assertions, 1 failures, 0 errors, 0 skips
```

今度は、テスト「should unfollow a user with Ajax」中で`DELETE`リクエストが発行されなくなりました。`RelationshipsController#destroy`中にある`current_user.unfollow(@user)`という処理が実行されず、`@user.following.count`の数が変化しなかった結果、テストが落ちたのですね。

```ruby:RelationshipsController#destroy
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
```

# 発展 - `FollowingTest`で、より適切なテストを行えるようにしてみる

Railsチュートリアル本文[リスト 14.40](https://railstutorial.jp/chapters/following_users?version=5.1#code-follow_button_tests)のテストでは、例えば`create.js.erb`や`destroy.js.erb`の記述内容にまで踏み込んだテストができていませんでした。別記事「[Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」 - FollowingTestの問題点と、その改良](https://qiita.com/rapidliner00/items/1d6d96996e2b241248e3)」にて、当該テストの問題点や改良策について記述しています。