Ruby
Rails
RSpec
FactoryGirl
テスト

RailsチュートリアルのテストをRSpecで実施 【Userモデル 単体テスト編 1/3]】

Userモデル

環境

Gemfile(一部抜粋)
# Gemfile
source 'https://rubygems.org'

gem 'rails',        '5.1.4'
gem 'rake',         '12.3.1'

group :development, :test do
  gem 'rspec-rails', '~> 3.7.2'
  gem "factory_bot_rails"
  gem 'spring-commands-rspec'
  gem 'rspec-its'
  gem "database_cleaner"
  gem 'pry-rails'
  gem 'pry-byebug' # デバッグを実施(Ruby 2.0以降で動作する)
end

group :test do
  gem 'capybara'
  gem "launchy", "~> 2.4.2"
  gem "selenium-webdriver", "~> 2.43.0"

  gem 'shoulda-matchers',
    git: 'https://github.com/thoughtbot/shoulda-matchers.git',
    branch: 'rails-5'
end

バージョン

$ gem list ^rails$ factory rspec capybara shoulda

  *** LOCAL GEMS ***

  rails (5.1.4)

  *** LOCAL GEMS ***

  factory_bot (4.10.0)
  factory_bot_rails (4.10.0)

  *** LOCAL GEMS ***

  rspec-core (3.7.1)
  rspec-expectations (3.7.0)
  rspec-its (1.2.0)
  rspec-mocks (3.7.0)
  rspec-rails (3.7.2)
  rspec-support (3.7.1)
  spring-commands-rspec (1.0.4)

  *** LOCAL GEMS ***

  capybara (3.4.1)

  *** LOCAL GEMS ***

  shoulda-matchers (3.1.2)

フォルダ、使用ファイル

種類 ファイル名
スペック spec/models/user_spec.rb
サポートモジュール spec/support/support_module.rb
shared_examples spec/support/shared_examples.rb
ファクトリ(ユーザ) spec/support/factories/users.rb
ファクトリ(マイクロポスト) spec/support/factories/microposts.rb

アウトライン作成 1/6

  • subjectの定義、その有効性の確認
  • 属性やメソッドの検証(respond_to)
user_spec.rb
# spec/models/user_spec.rb

RSpec.describe User, type: :model do

  # サブジェクト
  subject(:user) { build(:user) }
  # # or
  # let(:user) { build(:user) }
  # subject { user }

  # サブジェクトの有効性
  it { should be_valid }

  # 属性やメソッドの検証 (別ファイル: spec/support/shared_examples.rb )
  it_behaves_like "User-model respond to attribute or method"

end

shared_examples の作成

  • 属性・メソッドの検証をまとめる
shared_examples.rb
  # spec/support/shared_examples.rb
  # Userモデル

    # 属性・メソッドの検証
    shared_examples_for "User-model respond to attribute or method" do
      it { should respond_to(:name) }
      it { should respond_to(:email) }
      it { should respond_to(:password) }
      it { should respond_to(:password_confirmation) }
      it { should respond_to(:authenticate) }
      it { should respond_to(:password_digest) }
      it { should respond_to(:remember_digest) }
      it { should respond_to(:activation_digest) }
      it { should respond_to(:admin) }
      it { should respond_to(:microposts) }
      it { should respond_to(:feed) }
      it { should respond_to(:active_relationships) }
      it { should respond_to(:passive_relationships) }
      it { should respond_to(:following) }
      it { should respond_to(:followers) }
      it { should respond_to(:follow) }
      it { should respond_to(:unfollow) }
      it { should respond_to(:following?) }
    end

実行結果 1/6

$ bin/rspec spec/models/user_spec.rb -e "User-model respond to attribute or method"

User
  behaves like User-model respond to attribute or method
    should respond to #name
    should respond to #email
    should respond to #password
    should respond to #password_confirmation
    should respond to #authenticate
    should respond to #password_digest
    should respond to #remember_digest
    should respond to #activation_digest
    should respond to #admin
    should respond to #microposts
    should respond to #feed
    should respond to #active_relationships
    should respond to #passive_relationships
    should respond to #following
    should respond to #followers
    should respond to #follow
    should respond to #unfollow
    should respond to #following?

Finished in 1.46 seconds (files took 2.1 seconds to load)
18 examples, 0 failures

アウトライン作成 2/6

  • バリデーション:存在性、文字数、フォーマット、一意性、大文字小文字
user_spec.rb
# spec/models/user_spec.rb
RSpec.describe User, type: :model do

  # (省略)

  # validations
  describe "validations"
    # 存在性 presence
    describe "presence"
      # 名前、メールアドレス
      it "name and email should not to be empty/falsy"
      # パスワード、パスワード確認
      context "when password and confirmation is not present"
        it "@user is inavlid"
    # 文字数 characters
    describe "characters"
      # 名前: 最大 50 文字
      context "when name is too long"
        it "@user is inavlid"
      # メールアドレス: 最大 255 文字
      context "when email is too long"
        it "@user is inavlid"
      # パスワード、パスワード確認: 最小 6 文字
      describe "when password is too short"
        it "@user is inavlid"
    # email のフォーマット
    describe "email format"
      # invalid なフォーマット
      context "when invalid format"
        it "@user is inavlid"
      # valid なフォーマット
      context "when valid format"
        it "@user is valid"
    # email 一意性 unique
    describe "email uniqueness"
      # email が 大文字の場合
      context "when email is upcase"
        # 重複(invalid)となること
        it "should already taken (uniqueness case insensitive)"
      # 大文字小文字が混在(before_action)
      context "when mixed-case"
        # 小文字でDBに保存される
        it "should be saved as lower-case"
end

スペック作成 2/6

  • 存在性 presence、文字数 characters に関しては、Shoulda Matchers を導入して短く。

  • email 一意性 unique に関しては、FactoryBot を使用するとエラーオブジェクト(user.errors)が得られないので使用しない書き方に。

user_spec.rb
# spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do

  # (省略)

  # validations
  describe "validations" do

    # 存在性 presence
    describe "presence" do
      # 名前、メールアドレス
      it { should validate_presence_of :name }
      it { should validate_presence_of :email }
      # パスワード、パスワード確認
      context "when password and confirmation is not present" do
        before { user.password = user.password_confirmation =  "  " }
        it { should_not be_valid }
      end
    end
    # 文字数 characters
    describe "characters" do
      it { should validate_length_of(:name).is_at_most(50) }
      it { should validate_length_of(:email).is_at_most(255) }
      it { should validate_length_of(:password).is_at_least(6) }
    end
    # email のフォーマット
    describe "email format" do
      # 無効なフォーマット
      context "when invalid format" do
        # 無効なオブジェクト
        it "should be invalid" do
          invalid_addr = %w[user@foo,com user_at_foo.org example.user@foo. foo@bar_baz.com foo@bar+baz.com]
          invalid_addr.each do |addr|
            user.email = addr
            expect(user).not_to be_valid
          end
        end
      end
      # 有効なフォーマット
      context "when valid format" do
        # 有効なオブジェクト
        it "should be valid" do
          valid_addr = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
          valid_addr.each do |addr|
            user.email = addr
            expect(user).to be_valid
          end
        end
      end
    end
    # email 一意性 unique
    describe "email uniqueness" do
      # 重複
      context "when email is duplicate and upcase" do
        it "should already taken (uniqueness case insensitive)" do
          user = User.create(name: "foobar", email: "foo@bar.com", password: "foobar")
          dup_user = User.new(name: user.name, email: user.email.upcase, password: user.password)
          expect(dup_user).not_to be_valid
          expect(dup_user.errors[:email]).to include("has already been taken")
        end
      end
      # 大文字小文字が混在(before_action)
      context "when mixed-case" do
        let(:mixed_case_email) { "Foo@ExAMPle.CoM" }
        # 小文字でDBに保存される
        it "should be saved as lower-case" do
          user.email = mixed_case_email
          user.save
          expect(user.reload.email).to eq mixed_case_email.downcase
        end
      end
    end
  end
end

実行結果 2/6

$ bin/rspec spec/models/user_spec.rb -e "validations"

User
  validations
    presence
      should validate that :name cannot be empty/falsy
      should validate that :email cannot be empty/falsy
      when password and confirmation is not present
        should not be valid
    characters
      should validate that the length of :name is at most 50
      should validate that the length of :email is at most 255
      should validate that the length of :password is at least 6
    email format
      when invalid format
        should be invalid
      when valid format
        should be valid
    email uniqueness
      when email is duplicate and upcase
        should already taken (uniqueness case insensitive)
      when mixed-case
        should be saved as lower-case

Finished in 2.61 seconds (files took 2.47 seconds to load)
10 examples, 0 failures


アウトライン作成 3/6

  • パスワード認証 (has_secure_password, authenticate?メソッド)
user_spec.rb
# spec/models/user_spec.rb
RSpec.describe User, type: :model do

  # (省略)

  # パスワード認証 (has_secure_password)
  describe "has_secure_password"
    # パスワード確認が不一致
    context "when mismatched confirmation"
      it "@user is inavlid"
  # パスワード認証 (authenticate?)
  describe "authenticate? method"
    # 正しいパスワード
    context "with valid password"
      # 認証が 成功
      it "success authentication"
      # 誤ったパスワード
    context "with invalid password"
      it "fail authentication"

end

スペック作成 3/6

user_spec.rb
# spec/models/user_spec.rb
RSpec.describe User, type: :model do

  # (省略)

  # パスワード認証 (has_secure_password)
  describe "has_secure_password" do
    context "when mismatched confirmation" do
      before { user.password_confirmation = "mismatch" }
      it { should_not be_valid }
    end
  end
  # パスワード認証 (authenticate?)
  describe "authenticate? method" do
    before { user.save }
    let(:found_user) { User.find_by(email: user.email) }
    context "with valid password" do
      it "success authentication" do
        should eq found_user.authenticate(user.password)
      end
      it { expect(found_user).to be_truthy }
      it { expect(found_user).to be_valid }
    end
    context "with invalid password" do
      let(:incorrect) { found_user.authenticate("aaaaaaa") }
      it "fail authentication" do
        should_not eq incorrect
      end
      it { expect(incorrect).to be_falsey }
    end
  end
end

実行結果 3/6

$ bin/rspec spec/models/user_spec.rb -e "authenticate? method"

User
  authenticate? method
    with valid password
      success authentication
      should be truthy
      should be valid
    with invalid password
      fail authentication
      should be falsey

Finished in 0.88853 seconds (files took 2.9 seconds to load)
5 examples, 0 failures


アウトライン作成 4/6

  • マイクロポストの降順表示、ユーザ削除に依存してのdestroy
user_spec.rb
# spec/models/user_spec.rb
RSpec.describe User, type: :model do

  # (省略)

  # マイクロポスト
  describe "micropost association"
    # 降順に表示されること
    it "order descending"
    # ユーザが破棄されるとマイクロポストも破棄される
    it "should destroy micropost (depend on destroy user)"

end

スペック作成 4/6

user_spec.rb
# spec/models/user_spec.rb
RSpec.describe User, type: :model do

  # (省略)

  # マイクロポスト
  describe "micropost association" do
    before { user.save }
    # 今日の投稿/昨日の投稿
    let(:new_post) { create(:user_post, :today ) }
    let(:old_post) { create(:user_post, :yesterday ) }
    # 降順に表示されること
    it "order descending" do
      # インスタンス変数を呼び出さないと user.microposts.count が 0
      new_post
      old_post
      # (セットアップの確認)
      expect(user.microposts.count).to eq 2
      expect(Micropost.all.count).to eq user.microposts.count
      expect(user.microposts.to_a).to eq [new_post, old_post]
    end
    # ユーザが破棄されるとマイクロポストも破棄される
    it "should destroy micropost (depend on destroy user)" do
      new_post
      old_post
      my_posts = user.microposts.to_a
      user.destroy
      # ※ ユーザのマイクロポストはもとから空ではないことの確認
      expect(my_posts).not_to be_empty
      user.microposts.each do |post|
        expect(Micropost.where(id: post.id)).to be_empty
      end
    end
  end
end

ファクトリの作成(ユーザ/マイクロポスト)

  • before { user.save }
  # spec/factories/users.rb
  FactoryBot.define do
    # 自分
    # factory [任意のファクトリ名], class: [クラス名]
    factory :user, class: User do
      name     "Example user"
      email    "user@example.com"
      password              "foobar"
      password_confirmation "foobar"
      admin false
      # 他人
      factory :other_user do
        name { Faker::Name.name }
        email { Faker::Internet.email }
      end
    end
  end
trait を使用し、同じファクトリ名の特定のカラムを変更
  • let(:new_post) { create(:user_post, :today ) }
  • let(:old_post) { create(:user_post, :yesterday ) }
  # spec/factories/microposts.rb
  FactoryBot.define do
    # 自分のマイクロポスト
    factory :user_post, class: Micropost do
      content { Faker::Lorem.sentence(5) }
      # association :[関連するモデル名], factory: :[任意につけたファクトリ名]
      association :user, factory: :user  # or
      # [任意につけたファクトリ名]
      # user

      # 今日、投稿されたマイクロポスト
      trait :today do
        created_at 1.hour.ago
      end
      # 昨日、投稿されたマイクロポスト
      trait :yesterday do
        created_at 1.day.ago
      end
    end
  end
コンソールで確認
  • ファクトリの使い方について(FactoryBot)
  (抜粋)
  $ rails console test --sandbox

  [2] pry(main)> my_post = FactoryBot.create(:user_post)

  => #<Micropost:0x007ff569005f08
   id: 1,
   content: "Ut possimus cupiditate rem assumenda.",
   user_id: 5,
   created_at: Tue, 31 Jul 2018 11:11:33 UTC +00:00,
   updated_at: Tue, 31 Jul 2018 11:11:33 UTC +00:00,
   picture: nil>

  [5] pry(main)> u1 = FactoryBot.create(:other_user)

  => #<User:0x007ff569bb2e28
   id: 6,
   name: "Les Wolff",
   email: "monroe@blick.io",
   created_at: Tue, 31 Jul 2018 11:14:20 UTC +00:00,
   updated_at: Tue, 31 Jul 2018 11:14:20 UTC +00:00,
   password_digest: "$2a$04$FBvog4q90YTRi60q2W99yOlN/WltencHpGonMxb9qCS4NMZggVw/K",
   remember_digest: nil,
   admin: false,
   activation_digest: "$2a$04$7rOSnpywLU.hXGPRSaCjaOqCmfPZfRMyf2Ole43xa0xnI9zQQBhfq",
   activated: false,
   activated_at: nil>

実行結果 4/6 (エラー発生: email重複)

$ bin/rspec spec/models/user_spec.rb -e "micropost association"

User
  micropost association
    order descending (FAILED - 1)
    should destroy micropost (depend on destroy user) (FAILED - 2)

Failures:

  1) User micropost association order descending
     Failure/Error: let(:new_post) { create(:user_post, :today ) }

     ActiveRecord::RecordInvalid:
       Validation failed: Email has already been taken

原因

コンソールで確認

  • before { user.save } によってファクトリ名userが作成された後で、new_post が実行され、この時に再度同じファクトリを作成しようとしてしまい、email が重複してエラーになった。
  (抜粋)
  $ rails console test --sandbox

  [1] pry(main)>
  [2] pry(main)> user = FactoryBot.create(:user)
   id: 5,
   name: "Example user",
   email: "user@example.com",
   created_at: Tue, 31 Jul 2018 04:01:10 UTC +00:00,
   updated_at: Tue, 31 Jul 2018 04:01:10 UTC +00:00,
   password_digest: "$2a$04$VBfSSDbZ4C9g/JSTPcbsGuP1cZI.9jY1S7/TaQ/7IliHQm3ls2U4O",
   remember_digest: nil,
   admin: false,
   activation_digest: "$2a$04$oAo6RQDwYQW9pgy1.hibDeiPbSA5x69uoab7DM7dJq/A5AL/TRB/O",
   activated: false,
   activated_at: nil>
  [6] pry(main)> new_post = FactoryBot.create(:user_post, :today)
  ActiveRecord::RecordInvalid: Validation failed: Email has already been taken
  from /home/vagrant/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.1.4/lib/active_record/validations.rb:78:in `raise_validation_error'

対策:テストコード側を変更

サンプルデータ has_many な関係を定義する

  • new_post, old_post 作成時に、ファクトリ名user("Example user")のインスタンスを明示的に指定して、1人のユーザが、複数のマイクロポストを持っている状態(has_many)を実現する。

↓今のこのコードを

user_spec.rb
# spec/models/user_spec.rb
  describe "micropost association" do
    before { user.save }
    # 今日の投稿/昨日の投稿
    let(:new_post) { create(:user_post, :today) }
    let(:old_post) { create(:user_post, :yesterday) }

↓このように変更

# spec/models/user_spec.rb
  describe "micropost association" do
    before { user.save }
    # 今日の投稿/昨日の投稿
    # インスタンス変数(user)を明示的に指定し、1対多になるようにする
    let(:new_post) { create(:user_post, :today, user: user) }
    let(:old_post) { create(:user_post, :yesterday, user: user) }

コンソールで確認

  • ファクトリ名user(user_id: 5) が id: 1, id: 2 の2つのマイクロポストを持っている
(抜粋)
$ rails console test --sandbox

$ rails c test -s
[1] pry(main)>
[2] pry(main)> user = FactoryBot.create(:user)
=> #<User:0x007f871cb1c128
 id: 5,
 name: "Example user",
 email: "user@example.com",

[3] pry(main)>
[4] pry(main)> new_post = FactoryBot.create(:user_post, :today, user: user)
=> #<Micropost:0x007f871c78b040
 id: 1,
 content: "Dolor suscipit quisquam perspiciatis et.",
 user_id: 5,
 created_at: Tue, 07 Aug 2018 05:01:02 UTC +00:00,
 updated_at: Tue, 07 Aug 2018 06:01:25 UTC +00:00,
 picture: nil>
[5] pry(main)>
[6] pry(main)> old_post = FactoryBot.create(:user_post, :yesterday, user: user)
=> #<Micropost:0x007f871d628850
 id: 2,
 content: "Labore ducimus fuga voluptatem praesentium.",
 user_id: 5,
 created_at: Mon, 06 Aug 2018 06:01:02 UTC +00:00,
 updated_at: Tue, 07 Aug 2018 06:01:43 UTC +00:00,
 picture: nil>
[7] pry(main)>
[8] pry(main)>
[9] pry(main)> user.microposts.count
   (0.3ms)  SELECT COUNT(*) FROM "microposts" WHERE "microposts"."user_id" = ?  [["user_id", 5]]
=> 2
[10] pry(main)> user.microposts
=> [#<Micropost:0x007f871f350ea0
  id: 1,
  content: "Dolor suscipit quisquam perspiciatis et.",
  user_id: 5,
  created_at: Tue, 07 Aug 2018 05:01:02 UTC +00:00,
  updated_at: Tue, 07 Aug 2018 06:01:25 UTC +00:00,
  picture: nil>,
 #<Micropost:0x007f871f3566e8
  id: 2,
  content: "Labore ducimus fuga voluptatem praesentium.",
  user_id: 5,
  created_at: Mon, 06 Aug 2018 06:01:02 UTC +00:00,
  updated_at: Tue, 07 Aug 2018 06:01:43 UTC +00:00,
  picture: nil>]

実行結果 4/6 (修正後、成功)

$ bin/rspec spec/models/user_spec.rb -e "micropost association"

User
  micropost association
    order descending
    should destroy micropost (depend on destroy user)

Finished in 2.92 seconds (files took 2.42 seconds to load)
2 examples, 0 failures


アウトライン作成 5/6

  • フォロー/フォロー解除
user_spec.rb
# spec/models/user_spec.rb
RSpec.describe User, type: :model do

  # (省略)

  # フォロー/フォロー解除
  describe "follow and unfollow"
    # フォロー
    describe "follow"
      # 自分は他人をフォローしている(following?メソッド)
      it "user is following other-user (following? method)"
      # フォロー中のユーザの中に、他人が含まれている
      it "user's following include other-user (follow method)"
      # 他人のフォロワーの中に、自分が含まれている
      it "other-user's followers include user (follow method)"
    # フォロー解除
    describe "unfollow"
      # (follow と逆)
      it "user is not following other-user (following? method)"
      it "user's following does not include other-user (follow method)"
      it "other-user's followers does not include user (follow method)"

end

スペック作成 5/6

user_spec.rb
# spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do

  # (省略)

  # フォロー/フォロー解除
  describe "follow and unfollow" do
    let(:following) { create_list(:other_user, 30) }
    # let(:not_following) { create(:other_user) }
    before do
      user.save
      following.each do |u|
         user.follow(u) # => 自分が 30人をフォローする
         u.follow(user) # => 他人の 30人にフォローされる
      end
    end
    # フォロー
    describe "follow" do
      it "user is following other-user (following? method)" do
        following.each do |u|
          expect(user.following?(u)).to be_truthy
        end
      end
      it "user's following include other-user (follow method)" do
        following.each do |u|
          expect(user.following).to include(u)
        end
      end
      it "other-user's followers include user (follow method)" do
        following.each do |u|
          expect(u.followers).to include(user)
        end
      end
    end
    # フォロー解除
    describe "unfollow" do
      before do
        following.each do |u|
           user.unfollow(u) # => 自分が 30人をフォロー解除する
        end
      end
      it "user is not following other-user (following? method)" do
        following.each do |u|
          expect(user.following?(u)).to be_falsey
        end
      end
      it "user's following does not include other-user (follow method)" do
        following.each do |u|
          expect(user.following).not_to include(u)
        end
      end
      it "other-user's followers does not include user (follow method)" do
        following.each do |u|
          expect(u.followers).not_to include(user)
        end
      end
    end
  end
end

実行結果 5/6

$ bin/rspec spec/models/user_spec.rb -e "follow and unfollow"

User
  follow and unfollow
    follow
      user is following other-user (following? method)
      user's following include other-user (follow method)
      other-user's followers include user (follow method)
    unfollow
      user is not following other-user (following? method)
      user's following does not include other-user (follow method)
      other-user's followers does not include user (follow method)

Finished in 7.72 seconds (files took 2.08 seconds to load)
6 examples, 0 failures


アウトライン作成 6/6

  • マイクロポストフィード
user_spec.rb
# spec/models/user_spec.rb

RSpec.describe User, type: :model do

  # (省略)

  # マイクロポスト
  describe "micropost association"

    # (省略)

    # マイクロポストフィード
    describe "micropost feed"
      # 表示が正しいこと
      describe "have right microposts"
        # フォロー中のユーザのマイクロポスト
        it "following-user's post"
        # 自分自身のマイクロポスト
        it "my own post"
        # 未フォローのユーザのマイクロポストは非表示
        it "not have non-following-user's post"
end

サンプルデータのセットアップ

user_spec.rb
# spec/models/user_spec.rb

  # マイクロポスト
  describe "micropost association" do
    before { user.save }
    # マイクロポストフィード
    describe "micropost feed" d
      let(:following) { create_list(:other_user, 30) }
      let(:not_following) { create(:other_user) }
      # 関連付けされるファクトリを明示的に指定
      before do
        # 自分が10つのマイクロポストを保持
        create_list(:user_post, 10, user: user)
        # フォローしていないユーザが、10つのマイクロポストを保持
        create_list(:other_user_post, 10, user: not_following)
        following.each do |u|
           user.follow(u) # => 自分が 30人をフォローする
           u.follow(user) # => 他人の 30人にフォローされる
           create_list(:other_user_post, 3, user: u)
           # => 30人それぞれが、3つずつマイクロポストを保持
        end
      end
      # (セットアップの確認)
      it { expect(user.microposts.count).to eq 10 }
      it { expect(not_following.microposts.count).to eq 10 }
      # マイクロポストの合計が110
      it { expect(Micropost.all.count).to eq 110 }

実行結果
  $ bin/rspec spec/models/user_spec.rb -e "micropost feed"

  User
    micropost association
      micropost feed
        should eq 10
        should eq 10
        should eq 110

  Finished in 5.89 seconds (files took 1.98 seconds to load)
  3 examples, 0 failures

スペック作成 6/6

user_spec.rb
# spec/models/user_spec.rb
RSpec.describe User, type: :model do

  # (省略)

  describe "micropost association" do
    before { user.save }

    # (省略)

    # マイクロポストフィード
    describe "micropost feed" do
      let(:following) { create_list(:other_user, 30) }
      let(:not_following) { create(:other_user) }
      # 関連付けされるファクトリを明示的に指定
      before do
        # 自分が10つのマイクロポストを保持
        create_list(:user_post, 10, user: user)
        # フォローしていないユーザが、10つのマイクロポストを保持
        create_list(:other_user_post, 10, user: not_following)
        following.each do |u|
           user.follow(u) # => 自分が 30人をフォローする
           u.follow(user) # => 他人の 30人にフォローされる
           create_list(:other_user_post, 3, user: u)
           # => 30人それぞれが、3つずつマイクロポストを保持
        end
      end
      # (セットアップの確認)
      it { expect(user.microposts.count).to eq 10 }
      it { expect(not_following.microposts.count).to eq 10 }
      # マイクロポストの合計が100
      it { expect(Micropost.all.count).to eq 110 }

      describe "have right microposts" do
        it "following-user's post" do
          following.each do |u|
            u.microposts.each do |post|
              expect(user.feed).to include(post)
            end
          end
        end
        it "my own post" do
          user.microposts.each do |post|
            expect(user.feed).to include(post)
          end
        end
        it "not have non-following-user's post" do
          not_following.microposts.each do |post|
            expect(user.feed).not_to include(post)
          end
        end
      end
    end
  end
end

実行結果 6/6

$ bin/rspec spec/models/user_spec.rb -e "micropost feed"

User
  micropost association
    micropost feed
      should eq 10
      should eq 10
      should eq 110
      have right microposts
        following-user's post
        my own post
        not have non-following-user's post

Finished in 11.51 seconds (files took 2.01 seconds to load)
6 examples, 0 failures


参考


続く

Micropostモデル 単体テスト編 2/3