LoginSignup
18
16

More than 3 years have passed since last update.

【第1回】RSpecビギナーが、ビギナーなりにModelSpecを書いてみた

Last updated at Posted at 2020-09-27

はじめに

はじめまして。最初に自己紹介を簡単にさせて頂きます。
2020年5月〜2020年9月までDMMWEBCAMPにてRubyを中心に学習し、現在転職活動中の卒業生です。
先日、RSpecの雄である伊藤淳一 @jnchito さんのご厚意で開催された初学者向けの勉強会(RSpecビギナーズ!!)にも参加致しました。

ポートフォリオにRSpecを用いたテストを記述する中で感じた、
私と同じ初学者なら「ここでつまづくだろうな:thinking:」とか「ここが分からん:hugging:」といった"つまづきポイント"を稚拙ながら初学者目線でまとめました。
また、やっていく中で自分が書きたい機能の具体的なテストコード例が欲しいと思うことがあったので、自分の復習も兼ねてますが、この記事がRSpecビギナーズにとって少しでも参考になれば嬉しいなと思います。
※厚かましくはありますが、この記事をたまたま見た"RSpecエキスパート"がいらっしゃいましたら、下手くそなコードに是非アドバイスいただけると幸いです。

この記事で扱うこと

  • ModelSpec(モデルスペック)
    アプリケーションのコア部分であるモデルのテスト
  • モデルスペックの具体的な記述例
    自身のポートフォリオを参考にして記述していきます

この記事で扱わないこと

  • SystemSpec(システムスペック)
    モデル、コントローラ、ビュー、全部テストできるよ! 詳細はこちらをご覧下さい。
  • RSpecのセットアップ、準備
    (後述する参考書籍『EverydayRails-RSpecによるRailsテスト入門』にて詳しく記載してあるのでそちらを参考にしてください)

前提

  • 対象
    RSpec書こうとしてるけど何が何だかさっぱりなんじゃあ〜:hugging:という初学者の方。 ただ、伊藤さんの使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 こちらの記事内容をある程度は見ていたり、なんかやったことはあるな〜とか、最低限describe,it,expectの役割が分かる方が望ましいです。
  • 参考コード
    前述したように自身のポートフォリオを参考に記述するので、こちらにGitHubのリンクを貼っておきますが、テストの対象はサイトの基幹機能に絞っていますのでご了承ください。
    【サイトの基幹機能】(個人・法人会員の新規登録/ログイン/編集、法人会員登録の申請、記事の投稿/編集、DM、通知など)
    DM・通知などは次回SystemSpec編でご紹介します。

  • テストを記述するための準備
    RSpecによるテストを記述するためには、gem 'rspec-rails'をはじめ、いくつかgemを入れたり設定をする必要があります。
    まずはテストを書く準備を整えてからお読みください。
    必要なものは『EverydayRails-RSpecによるRailsテスト入門』に記載してあります。
    というか、これを見ればこの記事を見なくても分かる人は分かると思います。
    具体例としてコードを見たいという方はそのままお読みいただけると嬉しいです。(本当に参考程度ですが)

個人ユーザーモデルのテスト

①個人ユーザーモデルの紹介

まず初めに個人ユーザーモデルをお見せします。
実際はfavoriteモデルなどのアソシエーションもありますが、今回は扱わないので敢えて削除しております。

①user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :rooms, dependent: :destroy
  has_many :messages, dependent: :destroy
  has_many :notifications, dependent: :destroy

  validates :last_name, presence: true, length: { maximum: 10 }
  validates :first_name, presence: true, length: { maximum: 10 }
  validates :kana_last_name, presence: true, length: { maximum: 10 }
  validates :kana_first_name, presence: true, length: { maximum: 10 }
  validates :email, presence: true, length: { maximum: 30 }
  validates :postal_code, presence: true, length: { minimum: 7, maximum: 7 }
  validates :address, presence: true, length: { maximum: 30 }
  validates :phone_number, presence: true, length: { maximum: 12 }
  validates :introduction, length: { maximum: 200 }

  attachment :profile_image

  def full_name
    last_name + " " + first_name
  end

  def kana_full_name
    kana_last_name + " " + kana_first_name
  end
end

②FactoryBotを使用し、userデータをあらかじめ用意しておく

つまづきポイント:hugging:「FactoryBotとはなんぞや」
 FactoryBotについては『EverydayRails-RSpecによるRailsテスト入門』にも記載されています。
 gemを入れることで使えるようになるので、FactoryBotで検索してみてもいいかもです。
 私も最初分かりませんでしたが大丈夫です。
 単純にテスト用のデータを記述しておき、③の@userようなインスタンス変数やローカル変数に代入できたりする便利なものってだけです。
 使い方は③で記載します。まずは $ bin/rails g factory_bot:model user を実行するとファイルが作成されますので、作成されたら自身のアプリケーションのカラムに合わせてサンプルデータを入れてみてください。

②spec/factories/users.rb
FactoryBot.define do
  #FactoryBotを使用し、userデータをあらかじめ用意しておく
  factory :user do
    last_name { "テスト" }
    first_name { "太郎" }
    kana_last_name { "テスト" }
    kana_first_name { "タロウ" }
    email { "test@example.com" }
    postal_code { "1234567" }
    address { "東京都千代田区123-12-1" }
    phone_number { "12345678910" }
    password { "testtaro" }
  end
end

③具体的なテストの記述

いよいよテストを書いていきます。安心してください。後でちゃんとつまづきます。
$ bin/rails g rspec:model user を実行すると spec/modelsフォルダ内に user_spec.rbが作成されます。
モデルのテストコード(バリデーションや自身で作成したメソッドなど)はこちらのファイルに記述していきます。

以下、完成例です。

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

RSpec.describe User, type: :model do

  before do
    @user = FactoryBot.build(:user)
  end

  describe "バリデーションのテスト" do
    it "姓、名、カナ姓、カナ名、メール、郵便番号、住所、電話番号、パスワードがあれば有効な状態であること" do
      expect(@user).to be_valid
    end

    it "姓がなければ無効な状態であること" do
      @user.last_name = ""
      @user.valid?
      expect(@user.errors[:last_name]).to include("を入力してください")
    end

    it "姓が10文字以下でなければ無効であること" do
      @user.last_name = "a" * 11
      @user.valid?
      expect(@user.errors[:last_name]).to include("は10文字以内で入力してください")
    end

    it "名がなければ無効な状態であること" do
      @user.first_name = ""
      @user.valid?
      expect(@user.errors[:first_name]).to include("を入力してください")
    end

    it "名が10文字以下でなければ無効であること" do
      @user.first_name = "a" * 11
      @user.valid?
      expect(@user.errors[:first_name]).to include("は10文字以内で入力してください")
    end

    it "カナ姓がなければ無効な状態であること" do
      @user.kana_last_name = ""
      @user.valid?
      expect(@user.errors[:kana_last_name]).to include("を入力してください")
    end

    it "カナ姓が10文字以下でなければ無効であること" do
      @user.kana_last_name = "a" * 11
      @user.valid?
      expect(@user.errors[:kana_last_name]).to include("は10文字以内で入力してください")
    end

    it "カナ名がなければ無効な状態であること" do
      @user.kana_first_name = ""
      @user.valid?
      expect(@user.errors[:kana_first_name]).to include("を入力してください")
    end

    it "カナ名が10文字以下でなければ無効であること" do
      @user.kana_first_name = "a" * 11
      @user.valid?
      expect(@user.errors[:kana_first_name]).to include("は10文字以内で入力してください")
    end

    it "メールアドレスがなければ無効な状態であること" do
      @user.email = ""
      @user.valid?
      expect(@user.errors[:email]).to include("を入力してください")
    end

    it "メールアドレスが30文字以下でなければ無効な状態であること" do
      @user.email = "a" * 31
      @user.valid?
      expect(@user.errors[:email]).to include("は30文字以下で入力してください。")
    end

    it "郵便番号がなければ無効な状態であること" do
      @user.postal_code = ""
      @user.valid?
      expect(@user.errors[:postal_code]).to include("を入力してください")
    end

    it "郵便番号が7文字未満であれば無効な状態であること" do
      @user.postal_code = "a" * 6
      @user.valid?
      expect(@user.errors[:postal_code]).to include("は7文字以上で入力してください")
    end

    it "郵便番号が7文字を超えると無効な状態であること" do
      @user.postal_code = "a" * 8
      @user.valid?
      expect(@user.errors[:postal_code]).to include("は7文字以内で入力してください")
    end

    it "住所がなければ無効な状態であること" do
      @user.address = ""
      @user.valid?
      expect(@user.errors[:address]).to include("を入力してください")
    end

    it "住所が30文字以下でなければ無効な状態であること" do
      @user.address = "a" * 31
      @user.valid?
      expect(@user.errors[:address]).to include("は30文字以内で入力してください")
    end

    it "電話番号がなければ無効な状態であること" do
      @user.phone_number = ""
      @user.valid?
      expect(@user.errors[:phone_number]).to include("を入力してください")
    end

    it "電話番号が12文字以下でなければ無効な状態であること" do
      @user.phone_number = "a" * 13
      @user.valid?
      expect(@user.errors[:phone_number]).to include("は12文字以内で入力してください")
    end

    it "自己紹介文が200文字以下でなければ無効な状態であること" do
      @user.introduction = "a" * 201
      @user.valid?
      expect(@user.errors[:introduction]).to include("は200文字以内で入力してください")
    end

    it "パスワードが6文字以上でなければ無効であること" do
      @user.password = "a" * 5
      @user.valid?
      expect(@user.errors[:password]).to include("は6文字以上で入力してください。")
    end

    it "重複したメールアドレスなら無効な状態であること" do
      FactoryBot.create(:user)
      @user.valid?
      expect(@user.errors[:email]).to include("は既に存在します。")
    end
  end

  describe "インスタンスメソッドのテスト" do
    it "ユーザーのフルネームを文字列として返すこと" do
      @user.last_name = "テスト"
      @user.first_name = "太郎"
      expect(@user.full_name).to eq "テスト 太郎"
    end

    it "ユーザーのカナフルネームを文字列として返すこと" do
      @user.kana_last_name = "テスト"
      @user.kana_first_name = "タロウ"
      expect(@user.kana_full_name).to eq "テスト タロウ"
    end
  end

end

「ふむふむ、、テストコードはこのファイルに書くのね。了解了解:vulcan: って1行目からよくわからんやん! require 'rails_helper' って何やね〜ん:hugging:」って思った方。
これはRSpecに対し、ファイル内のテストを実行するためにRailsアプリケーションの読み込みが必要であることを伝えています。この記述はテストスイート内のほぼすべてのファイルで必要になります。(EverydayRailsから引用)
なんかEverydayRailsあれば別に説明要らなくね...?と思い始めてきましたが、めげずに頑張ります。

要するに require 'rails_helper' という記述をすることで、RSpec先輩に対し
「僕のアプリケーションこんな感じです!モデルにはこんなデータがあって、コントローラはこんな記述してて、ビューではこんなものを表示させてます!把握お願いします!」
ということを伝えています。
この記述のおかげで、RSpecがアプリケーションと記述したコードを照らし合わせてテストを行ってくれます。

じゃあ、その rails_helper って何なの?って思った方。
Rspecの設定はspecフォルダ内の rails_helper.rb に書いたりします。
次回記載予定のシステムスペックなどで使用する、deviseのヘルパーメソッドをsystem_spec内で使用可能にする設定もここに書いています。

この設定の部分などについてもEverydayRailsに記載されてます。
もう本当にEverydayRails頼りになってますが、それくらいテストコードの具体的な記述や、なぜこの記述が必要か、などの情報が網羅されている書籍なのでおすすめです。

spec/rails_helper.rb
# deviseのヘルパーメソッドをsystem_spec内で使用可能にする
  config.include Devise::Test::IntegrationHelpers, type: :system



「ほうほう、rails_helperについては何となく分かった。でもその下の完成コードだけ示されてもよく分からんよう:hugging:」って方もいると思うので、一応簡単に説明はしていきます。
ただ、ユーザーモデルのテストに関してはEverydayRailsにより詳しく書いているので、そちらを参考にした方がいいかもです。

以下のコード例をご覧下さい。
まず、beforeブロックは同ファイル内の複数のテストで同じデータが必要な場合に、コードをDRYにする為に使用したりします。
今回の場合、右辺の FactoryBot.build(:user) という記述によって、先程の②で記述したspec/factories/users.rbのテストデータをbuildして@userに代入してあげています。
そうすることで、後のitブロックなどで@userが使えるようになります。
itブロックの1行目に @user.last_name = "" で last_name のデータを空のまま上書きして、「@user.last_nameがnilなら無効だよ〜」とか、 @user.last_name = "a" * 11 で last_name に "aaaaaaaaaaa" というデータを上書きし、「last_nameはバリデーションかけてるから10文字以下じゃないと無効だよ〜」とかのテストを書いているという感じです。
ちなみにその前にある be_valid はマッチャといいます。
マッチャに関しても伊藤さんの使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」
を参考にするといいかと思います。

つまづきポイント:hugging:「FactoryBotの記述位置」
また、RSpec.describe User, type: :model doの直下にbeforeブロックを置き、その中にFactoryBotで作成した@userを記述していることで、beforeブロック内の@userは、同ファイル内の全テストで使うことができるようになっています。
今回はまだ単純なアソシエーションのみなのでマシなのですが、アソシエーションが増えてくるにつれ、FactoryBotでデータを作成することが多くなり、その際に記述する位置がちゃんとしていないとハマります。
特にSystemSpecですね。沼でした。後に話していこうと思います。

③spec/models/user_spec.rb
RSpec.describe User, type: :model do
# beforeブロックでは同ファイル内の複数のテストで同じデータが必要な場合に、コードをDRYにする為に使用する。
 before do
   @user = FactoryBot.build(:user) # @userにはlast_name{ "テスト" }、first_name { "太郎" }とかのデータが入っている
 end

 it "姓、名、カナ姓、カナ名、メール、郵便番号、住所、電話番号、パスワードがあれば有効な状態であること" do
   expect(@user).to be_valid
 end

 it "姓がなければ無効な状態であること" do
   @user.last_name = "" # @user.last_nameがnilなら無効だよ〜
   @user.valid?
   expect(@user.errors[:last_name]).to include("を入力してください")
 end

 it "姓が10文字以下でなければ無効であること" do
   @user.last_name = "a" * 11 # last_nameはバリデーションかけてるから10文字以下じゃないと無効だよ〜
   @user.valid?
   expect(@user.errors[:last_name]).to include("は10文字以内で入力してください")
 end



また、以下のテストのみちょっと様子が違います。1行目にFactoryBot.create(:user)が挟まれていますね。
前述したbeforeブロックではitブロックの前に@userを作ってくれています。このテストもそうです。
ただし、今回のbeforeブロック内の@userはcreateではなく、buildなので、saveされない限りデータベースには保存されません。
以下のテストでは1行目でFactoryBot.create(:user)と記述することで、順番としてはbeforeブロック内の@userよりも先にこちらが保存されることになります。
その後に @user.valid?(@userは有効ですか?) と聞くと、先に同じデータが保存されているので、include("は既に存在します。")という文言が期待されるわけですね。

③spec/models/user_spec.rb
it "重複したメールアドレスなら無効な状態であること" do
  FactoryBot.create(:user) # 先に保存される
  @user.valid?
  expect(@user.errors[:email]).to include("は既に存在します。")
end

残りのitブロックも構造は同じで、恐らく何となくは理解できると思うので、端折ります。
注意したいのが、expect(エクスペクテーション)のinclude("を入力してください")の部分は、人によってそれぞれエラー文が異なるかと思うので、
i18nによるエラーの日本語化などしている方は devise.ja.yml などで確認、もしくは実行時のターミナルのエラー文にもヒントがあるのでそれぞれ確認して文言を入れてあげましょう。



最後にメソッドのテストをします。
伊藤さんもRSpecビギナーズの動画内で仰ってましたが、モデルのバリデーションのテストも大事だけど、自身で作成したメソッドなどをテストするのが大事ですとのこと。
また、アソシエーションのテストに関しては書かなくても良いレベルだそうです。
(ただし、名前の重複が起きてclass_nameなどを使い擬似的にモデルを作ったりした場合(フォロー機能など)は、やってもいいかなとのことでした。)
アソシエーションなどはRailsがよしなにしてくれている部分があるので、やるなら自身で作成したメソッドが正しく機能するかどうかを確かめるべきなのだそう。
確かに〜〜〜と思いました。動画内で仰っているので、是非見てみてください。
ということで、めちゃくちゃ簡単ですが以下にメソッドのテストも記述します。

③spec/models/user_spec.rb
describe "インスタンスメソッドのテスト" do
  it "ユーザーのフルネームを文字列として返すこと" do
    @user.last_name = "テスト"
    @user.first_name = "太郎"
    expect(@user.full_name).to eq "テスト 太郎"
  end

  it "ユーザーのカナフルネームを文字列として返すこと" do
    @user.kana_last_name = "テスト"
    @user.kana_first_name = "タロウ"
    expect(@user.kana_full_name).to eq "テスト タロウ"
  end
end

まあ、特に説明なしでも分かるくらい簡単なものなので敢えて説明はしません。
①のモデルに記述しているfull_nameメソッドとkana_full_nameメソッドがそれぞれちゃんとフルネームになっているか、というだけです。
メソッドのテストに関しては、複雑なものは私も書けていないので勉強します。。

法人ユーザーモデルのテスト

こちらも実際にはRelationshipモデルなどのアソシエーションがありますが、今回は扱わないので敢えて削除しております。
法人ユーザーのテストは個人ユーザーとほぼ一緒なのでコードのみ記載しますので以下ご参考までに。

①company.rb
class Company < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :rooms, dependent: :destroy
  has_many :messages, dependent: :destroy
  has_many :articles, dependent: :destroy
  has_many :notifications, dependent: :destroy

  validates :company_name, presence: true, length: { maximum: 30 }
  validates :kana_company_name, presence: true, length: { maximum: 30 }
  validates :email, presence: true, length: { maximum: 30 }
  validates :postal_code, presence: true, length: { minimum: 7, maximum: 7 }
  validates :address, presence: true, length: { maximum: 30 }
  validates :phone_number, presence: true, length: { maximum: 12 }
  validates :introduction, length: { maximum: 800 }

  attachment :profile_image
  attachment :background_image

  # approvedがtrueであればログイン可。新規登録時点ではdefaultがfalseなのでログインできない状態にする
  def active_for_authentication?
    super && self.approved?
  end

  # 上記でログインが弾かれた後のメッセージ。文言詳細は config/locales/devise.ja.yml に記載。
  def inactive_message
    self.approved? ? super : :needs_admin_approval
  end

  def followed_by?(user)
    passive_relationships.find_by(following_id: user.id).present?
  end
end
②spec/factories/companies.rb
FactoryBot.define do
  factory :company do
    company_name { "テスト株式会社" }
    kana_company_name { "テストカブシキガイシャ" }
    email { "testcompany@example.com" }
    postal_code { "1234567" }
    address { "東京都千代田区123-12-1" }
    phone_number { "12345678910" }
    password { "testcompany" }
    approved { true }
    is_active { true }
  end
end
③spec/models/company_spec.rb
require 'rails_helper'

RSpec.describe Company, type: :model do

  describe "バリデーションのテスト" do

    before do
      @company = FactoryBot.build(:company)
    end

    it "企業名、企業カナ名、メール、郵便番号、住所、電話番号、パスワードがあれば有効な状態であること" do
      expect(@company).to be_valid
    end

    it "企業名がなければ無効な状態であること" do
      @company.company_name = ""
      @company.valid?
      expect(@company.errors[:company_name]).to include("を入力してください")
    end

    it "企業カナ名がなければ無効な状態であること" do
      @company.kana_company_name = ""
      @company.valid?
      expect(@company.errors[:kana_company_name]).to include("を入力してください")
    end

    it "メールアドレスがなければ無効な状態であること" do
      @company.email = ""
      @company.valid?
      expect(@company.errors[:email]).to include("を入力してください")
    end

    it "郵便番号がなければ無効な状態であること" do
      @company.postal_code = ""
      @company.valid?
      expect(@company.errors[:postal_code]).to include("を入力してください")
    end

    it "住所がなければ無効な状態であること" do
      @company.address = ""
      @company.valid?
      expect(@company.errors[:address]).to include("を入力してください")
    end

    it "電話番号がなければ無効な状態であること" do
      @company.phone_number = ""
      @company.valid?
      expect(@company.errors[:phone_number]).to include("を入力してください")
    end

    it "パスワードが6文字以上でなければ無効であること" do
      @company.password = "a" * 5
      @company.valid?
      expect(@company.errors[:password]).to include("は6文字以上で入力してください")
    end

    it "重複したメールアドレスなら無効な状態であること" do
      FactoryBot.create(:company)
      @company.valid?
      expect(@company.errors[:email]).to include("はすでに存在します")
    end

  end
end

ジャンルモデルのテスト

ジャンルも特に難しいことはしていないので説明は省きます。

①genre.rb
class Genre < ApplicationRecord
  has_many :articles, dependent: :destroy
  validates :genre_name, presence: true, length: { maximum: 15 }
end
②spec/factories/genres.rb
FactoryBot.define do
  factory :genre do
    genre_name { "テストジャンル" }
    is_active { true }
  end
end
③spec/models/genre_spec.rb
require 'rails_helper'

RSpec.describe Genre, type: :model do
  describe "バリデーションのテスト" do
    it "ジャンル名がなければ無効な状態であること" do
      @genre = FactoryBot.build(:genre)
      @genre.genre_name = ""
      @genre.valid?
      expect(@genre.errors[:genre_name]).to include("を入力してください")
    end
  end
end

記事モデルのテスト

①記事モデルの紹介

やっとここまできました。記事のモデルスペックは少しだけ異なる記述をしていたりするので簡単に説明していきたいと思います。
以下、記事モデルです。

①article.rb
class Article < ApplicationRecord
  belongs_to :company
  belongs_to :genre

  validates :title, presence: true, length: { maximum: 35 }
  validates :body, presence: true

  attachment :image

  # 掲載ステータスが有効かつジャンルが有効になっている記事のみ探す
  def self.all_active
    where(is_active: true).joins(:genre).where(genres: {is_active: true})
  end

  def favorited_by?(user)
    favorites.where(user_id: user.id).exists?
  end
end

②FactoryBotを使用し、articleデータをあらかじめ用意しておく

②spec/factories/articles.rb
FactoryBot.define do
  factory :article do
    title { "テストタイトル" }
    body { "テスト本文" }
    is_active { true }
    company
    genre
  end
end

③具体的なテストの記述

記事モデルのテストを書いていきます。
$ bin/rails g rspec:model article で spec/modelsフォルダ内に article_spec.rbが作成されます。
以下、完成コード例です。

③spec/models/article_spec.rb
require 'rails_helper'

RSpec.describe Article, type: :model do

  describe "Articleのテスト" do
    before do
      @company = FactoryBot.create(:company)
      @genre = FactoryBot.create(:genre)
      @article = FactoryBot.build(:article)
    end

    # article作成
    context "全てのデータが入っている場合" do
      it "全て入力してあるので保存される" do
        @article.company_id = @company.id
        @article.genre_id = @genre.id
        expect(@article.save).to be true
      end
    end

    context "全てのデータが入っていない場合" do
      it "全て入力されていないので保存されない" do
        @article.company_id = @company.id
        @article.genre_id = @genre.id
        @article.title = ""
        @article.body = ""
        expect(@article.save).to be false
      end
    end
  end

  describe "バリデーションのテスト" do
    before do
      @article = FactoryBot.build(:article)
    end

    context "タイトルが存在しない場合" do
      it "無効な状態であること" do
        @article.title = ""
        @article.valid?
        expect(@article.errors[:title]).to include("を入力してください")
      end
    end

    context "タイトルが35文字を超える場合" do
      it "エラーメッセージが出ること" do
        @article.title = "a" * 36
        @article.valid?
        expect(@article.errors[:title]).to include("は35文字以内で入力してください")
      end
    end

    it "本文がなければ無効な状態であること" do
      @article.body = ""
      @article.valid?
      expect(@article.errors[:body]).to include("を入力してください")
    end

  end
end

まあ、そこまで目新しいものはないのですが記事モデルは①でも記載している通り
belongs_to :company
belongs_to :genre
というようなアソシエーションになっており、今までとは少しだけ異なります。
以下の通りbeforeブロックでは先にcompanyとgenreをFactoryBotでcreateし、データベースに保存したうえで、@company@genreにそれぞれ代入しています。
記事投稿の流れは
会社が居て→記事ジャンルを選択し→記事を投稿できる
という流れになっています。
これを踏まえると、記事の投稿を行うには法人とジャンルが存在しなければならないので、beforeブロックは以下のような記述になっています。

あとは簡単ですね。
1.どの企業が投稿した記事か, 2.記事のジャンルはどれか
それぞれ@article.company_id、@article.genre_id に代入してあげて、
1.全てのデータが入っている場合, 2.全てのデータが入っていない場合 にcontextで分けてテストするだけです!

③spec/models/article_spec.rb
RSpec.describe Article, type: :model do

  describe "Articleのテスト" do
    before do
      # 会社が居て→記事ジャンルを選択し→記事を投稿できる
      @company = FactoryBot.create(:company)
      @genre = FactoryBot.create(:genre)
      @article = FactoryBot.build(:article)
    end

    # article作成
    context "全てのデータが入っている場合" do
      it "全て入力してあるので保存される" do
        @article.company_id = @company.id
        @article.genre_id = @genre.id
        expect(@article.save).to be true
      end
    end

    context "全てのデータが入っていない場合" do
      it "全て入力されていないので保存されない" do
        @article.company_id = @company.id
        @article.genre_id = @genre.id
        @article.title = ""
        @article.body = ""
        expect(@article.save).to be false
      end
    end
  end

これでモデルのテストは終わりです!
メッセージモデルのモデルスペックもあるのですが、項目数が少ないため省略致します。
気になる方はGitHubからご覧ください。

終わりに

今回はアプリケーションのコア部分であるモデルのテスト、モデルスペックについてまとめました。
モデルスペックも大事なのですが、特に大事なのは次回記載するシステムスペックになります。
ユーザーがマイページを編集したり、法人にDMを送ったりなどの、実際のブラウザでの動きをテストします。
つまづくポイントも多かったです。私だけかもしれませんが:thinking:
なるべく分かりやすくまとめていければと思いますので、よろしければ次回も見ていただけると嬉しいです。
最後までご覧いただきありがとうございました。

2020.09.28 システムスペック編を掲載しました。こちらをご覧下さい。

参考記事

https://qiita.com/jnchito/items/2a5d3e15761fd413657a
https://qiita.com/jnchito/items/42193d066bd61c740612
https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5

18
16
2

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
18
16