【RSpec】Railsチュートリアル第6版　第14章

#はじめに
Railsチュートリアル第6版のテストをRSpecで書き直していく。

###目次
- [第3章](https://qiita.com/supyolo888/items/35081ead2cfafb40a995)
- [第4章](https://qiita.com/supyolo888/items/939b670b62eb18467f6d)
- [第5章](https://qiita.com/supyolo888/items/42234ac2ed3919b14586)
- [第6章](https://qiita.com/supyolo888/items/bd10e64c77fe391cf203)
- [第7章](https://qiita.com/supyolo888/items/d2824ced26402b5f58d1)
- [第8章](https://qiita.com/supyolo888/items/508caaa40edf307093ed)
- [第9章](https://qiita.com/supyolo888/items/62e471b07a0c9a81575d)
- [第10章](https://qiita.com/supyolo888/items/a8695b20ec8082f85223)
- [第11章](https://qiita.com/supyolo888/items/5c81331e46f1d9d5ab3c)
- [第12章](https://qiita.com/supyolo888/items/fbb95671d39dfbf9df90)
- [第13章](https://qiita.com/supyolo888/items/c88b6acd4ff55750defc)
- 第14章（本記事）

#Minitest

###Relationshipモデルのテスト

```rb:test/models/relationship_test.rb
require 'test_helper'

class RelationshipTest < ActiveSupport::TestCase

  def setup
    @relationship = Relationship.new(follower_id: users(:michael).id,
                                     followed_id: users(:archer).id)
  end

  test "should be valid" do
    assert @relationship.valid?
  end

  test "should require a follower_id" do
    @relationship.follower_id = nil
    assert_not @relationship.valid?
  end

  test "should require a followed_id" do
    @relationship.followed_id = nil
    assert_not @relationship.valid?
  end
end
```

###“following” 関連のメソッドのテスト

```rb:test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  .
  .
  .
  test "should follow and unfollow a user" do
    michael  = users(:michael)
    archer   = users(:archer)
    assert_not michael.following?(archer)
    michael.follow(archer)
    assert michael.following?(archer)
    assert archer.followers.include?(michael)
    michael.unfollow(archer)
    assert_not michael.following?(archer)
  end

  test "feed should have the right posts" do
    michael = users(:michael)
    archer  = users(:archer)
    lana    = users(:lana)
    # フォローしているユーザーの投稿を確認
    lana.microposts.each do |post_following|
      assert michael.feed.include?(post_following)
    end
    # 自分自身の投稿を確認
    michael.microposts.each do |post_self|
      assert michael.feed.include?(post_self)
    end
    # フォローしていないユーザーの投稿を確認
    archer.microposts.each do |post_unfollowed|
      assert_not michael.feed.include?(post_unfollowed)
    end
  end
end
```

###フォロー/フォロワーページの認可のテスト

```rb:test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect following when not logged in" do
    get following_user_path(@user)
    assert_redirected_to login_url
  end

  test "should redirect followers when not logged in" do
    get followers_user_path(@user)
    assert_redirected_to login_url
  end
end
```

###following/followerをテストするためのリレーションシップ用fixture

```test/fixtures/relationships.yml
one:
  follower: michael
  followed: lana

two:
  follower: michael
  followed: malory

three:
  follower: lana
  followed: michael

four:
  follower: archer
  followed: michael
```

###following/followerページのテスト

```rb:test/integration/following_test.rb
require 'test_helper'

class FollowingTest < ActionDispatch::IntegrationTest

  def setup
    @user  = users(:michael)
    @other = users(:archer)
    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)
    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)
    end
  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
```

###リレーションシップの基本的なアクセス制御に対するテスト

```rb:test/controllers/relationships_controller_test.rb
require 'test_helper'

class RelationshipsControllerTest < ActionDispatch::IntegrationTest

  test "create should require logged-in user" do
    assert_no_difference 'Relationship.count' do
      post relationships_path
    end
    assert_redirected_to login_url
  end

  test "destroy should require logged-in user" do
    assert_no_difference 'Relationship.count' do
      delete relationship_path(relationships(:one))
    end
    assert_redirected_to login_url
  end
end
```

#RSpec

###Relationshipモデルのテスト

```rb:spec/models/relationship_spec.rb
require 'rails_helper'

RSpec.describe Relationship, type: :model do
  
  let(:user) { FactoryBot.create(:user) }
  let(:other) { FactoryBot.create(:user) }
  let(:relationship) { Relationship.new(follower_id: user.id,followed_id: other.id) }

  it "should be valid" do
    expect(relationship).to be_valid
  end

  it "should require a follower_id" do
    relationship.follower_id = nil
    expect(relationship).to be_invalid
  end

  it "should require a followed_id" do
    relationship.followed_id = nil
    expect(relationship).to be_invalid
  end

end
```

`let`でrelationshipを作成しておく。

###“following” 関連のメソッドのテスト

```rb:spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  .
  .
  .
  describe "relationships" do

    let(:michael) { FactoryBot.create(:user) }
    let(:archer) { FactoryBot.create(:user) }
    let(:lana) { FactoryBot.create(:user) }

    before do
      michael.follow(lana)
    end

    it "should follow and unfollow a user" do
      expect(michael.following?(archer)).to be_falsy
      michael.follow(archer)
      expect(michael.following?(archer)).to be_truthy
      expect(archer.followers.include?(michael)).to be_truthy
      michael.unfollow(archer)
      expect(michael.following?(archer)).to be_falsy
    end

    it "feed should have the right posts" do  
      lana.microposts.each do |post_following|
        expect(michael.feed.include?(post_following)).to be_truthy
      end
      michael.microposts.each do |post_self|
        expect(michael.feed.include?(post_self)).to be_truthy
      end
      archer.microposts.each do |post_unfollowed|
        expect(michael.feed.include?(post_unfollowed)).to be_falsy
      end
    end

  end

end
```

relationshipのfixtureが無いので、`before　do`でfollowしておく。


###フォロー/フォロワーページの認可のテスト

```rb:spec/requests/users_spec.rb
require 'rails_helper'

RSpec.describe "Users", type: :request do

  before do
  @user = FactoryBot.create(:user)
  @noadmin_user = FactoryBot.create(:user, :noadmin)
  end
  .
  .
  .
  it "should redirect following when not logged in" do
    get following_user_path(@user)
    expect(response).to redirect_to login_url
  end

  it "should redirect followers when not logged in" do
    get followers_user_path(@user)
    expect(response).to redirect_to login_url
  end

end
```

###following/followerページのテスト

```rb:spec/requests/followings_spec.rb
require 'rails_helper'

RSpec.describe "Followings", type: :request do
  
  let(:user) { FactoryBot.create(:user) }
  let(:other) { FactoryBot.create(:user) }
  let(:other2) { FactoryBot.create(:user) }
  let(:other3) { FactoryBot.create(:user) }

  before do
    log_in_as(user)
    user.follow(other2)
    user.follow(other3)
    other2.follow(user)
    other.follow(user)
  end

  it "following page" do
    get following_user_path(user)
    expect(user.following.empty?).to be_falsy
    assert_match user.following.count.to_s, response.body
    user.following.each do |user|
      assert_select "a[href=?]", user_path(user)
    end
  end

  it "followers page" do
    get followers_user_path(user)
    expect(user.followers.empty?).to be_falsy
    assert_match user.followers.count.to_s, response.body
    user.followers.each do |user|
      assert_select "a[href=?]", user_path(user)
    end
  end

  it "should follow a user the standard way" do
    expect{
      post relationships_path, params: { followed_id: other.id }
    }.to change(user.following, :count).by(1)
  end

  it "should follow a user with Ajax" do
    expect{
      post relationships_path, xhr: true, params: { followed_id: other.id }
    }.to change(user.following, :count).by(1)
  end

  it "should unfollow a user the standard way" do
    user.follow(other)
    relationship = user.active_relationships.find_by(followed_id: other.id)
    expect{
      delete relationship_path(relationship)
    }.to change(user.following, :count).by(-1)
  end

  it "should unfollow a user with Ajax" do
    user.follow(other)
    relationship = user.active_relationships.find_by(followed_id: other.id)
    expect{
      delete relationship_path(relationship), xhr: true
    }.to change(user.following, :count).by(-1)
  end

end
```

こちらもrelationship fixtureの代わりに、`before do`でフォロー関係を作成しておく。

###リレーションシップの基本的なアクセス制御に対するテスト

```rb:spec/requests/relationships_spec.rb
require 'rails_helper'

RSpec.describe "Relationships", type: :request do

  let(:user) { FactoryBot.create(:user) }
  let(:other) { FactoryBot.create(:user) }
  let!(:relationship) {Relationship.create(follower_id: user.id,followed_id: other.id)}
  
  it "create should require logged-in user" do
    expect{
      post relationships_path
    }.to_not change(Relationship, :count)
    expect(response).to redirect_to login_path
  end

  it "destroy should require logged-in user" do
    expect{
      delete relationship_path(relationship)
    }.to_not change(Relationship, :count)
    expect(response).to redirect_to login_path
  end

end
```

*"destroy should require logged-in user"*において使用する、userがotherをフォローしているというrelationshipを`let!`で作成しておく。この際、userのログインが求められないように、`Relationship.create`を使用する。

#おわりに
今回テストを記述するにあたって、チュートリアルのテスト内容を忠実に再現することを意識していたため、RSpecの強みや良さを活かしきれていない部分が多々あると思う。
より良い書き方があれば是非コメントしていただきたい。
また、演習のテストは無視したので、気が向けば追加するつもりだ。