Rspecを書くときに参考にしたQiita記事

# セッティングから基礎知識まで

https://qiita.com/morrr/items/6ed889d17abbfd352649


# 詳細

everyday Rspec


```ruby:user_spec.rb
describe User do
  # 姓、名、メール、パスワードがあれば有効な状態であること
  it "is valid with a first name, last name, email, and password"
  # 名がなければ無効な状態であること
  it "is invalid without a first name"
  # 姓がなければ無効な状態であること
  it "is invalid without a last name"
  # メールアドレスがなければ無効な状態であること
  it "is invalid without an email address"
  # 重複したメールアドレスなら無効な状態であること
  it "is invalid with a duplicate email address"
  # ユーザーのフルネームを文字列として返すこと
  it "returns a user's full name as a string"
end
```

## 説明
①期待する結果をまとめて記述(describe)している。 
このケースでは`Userモデル`がどんなモデルなのか、そしてどんな振る舞いをするのかということを説明している。

②example(itで始まる1行)一つにつき、結果を一つだけ期待している。
`first_name,` `last_name,` `email`のバリデーションをそれぞれ分けてテストしている点に注意する。
こうすれば、example が失敗したときに問題が起きたバリデーションを特定できる。
原因調査のためにRSpecの出力結果を調べる必要はありません。
少なくともそこまで細かく調べずに済む。

③どの`example`も明示的である。技術的なことを言うと、`it`のあとに続く説明用の文字列は必須ではない。
しかし、省略してしまうとスペックが読みにくくなるため記述する。

④各`example`の説明は動詞で始まっている。`should`ではない。
期待する結果を声に出して読んでみると、下記の通り。
`User is invalid without a first name (名がなければユーザーは無効な状態である)`
`User is invalid without a last name　(姓がなければユーザーは無効な状態である)`
`User returns a user’s full name as a string (ユーザーは文字列としてユーザーのフルネームを返す)`
可読性は 非常に重要であり、RSpec のキーとなる機


# モデルのexpectとis_expectedの違い

https://qiita.com/mm36/items/453d71d48dd8f462344c


# belongs_toをチェックしたい

https://qiita.com/EastResident/items/e97632074f50d2541547


# carrierwaveのテストをしたい

```ruby:factories/samples_files.rb
FactoryBot.define do
  factory :sample_file do
    id: 1
    file Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/sample.pdf'))
  end
end
```

https://qiita.com/motty93/items/5cbc46ce79a502fcaa65

しかし、↑のファイル指定はrubocopに怒られた、、
参考：https://github.com/rubocop-hq/rubocop/issues/3779

```ruby:修正版
FactoryBot.define do
  factory :sample_file do
    id: 1
    file Rack::Test::UploadedFile.new(Rails.root.join('spec', 'fixtures', 'sample.pdf'))
  end
end
```


# uniqunessのテストをしたい


# この処理繰り返し使いたい、、

shared_contextを使うと良さげです。
https://magazine.rubyist.net/articles/0035/0035-RSpecInPractice.html
[Rspecのshared_context基礎の基礎
](https://qiita.com/kawakubox/items/5e3898bf1e4db1169008)


```ruby:spec/models/user_spec.rb

require 'rails_helper'

describe Person do
  let(:person) { Person.new(age) }

  shared_context 'infancy' do
    let(:age) { 19 }
  end

  shared_context 'adult' do
    let(:age) { 20 }
  end

  describe '#auth_smoke?' do
    context 'when age greater equals to 20' do
      include_context 'adult'
      subject { person.auth_smoke? }
      it { is_expected.to be_truthy }
    end
    context 'when age less than 20' do
      include_context 'infancy'
      subject { person.auth_smoke? }
      it { is_expected.to be_falsy }
    end
  end
end
```


# let!とbeforeの違いって何？

https://qiita.com/kiyodori/items/8322d770783b3be95682

結論：書いた順に評価される
let!が先であれば、beforeに先立つし、逆であれば、beforeが先に評価される。
明確な違いが無いのであろうか。。？


# subjectとis_expectedをセットで使う

```ruby
subject(:user) { User.new(email: "hoge@example.com", password: "password") }

```

https://qiita.com/mm36/items/453d71d48dd8f462344c
http://kei-p3.hatenablog.com/entry/2016/05/25/235124


# 特定のテストをスキップしたいとき

pendingを用いて、テストが失敗しないようにする。

https://qiita.com/tyamagu2/items/fe75ebfcfd643d3d760d


ちなみに、`xit`とするだけで、特定のexampleをスキップ可能なので覚えておく。

# Rspecでenumを使いたいとき


[enumで数値型カラムを作る際に、テストデータを作る時に注意すること](https://doruby.jp/users/ishizu/entries/enum%E3%81%A7%E6%95%B0%E5%80%A4%E5%9E%8B%E3%82%AB%E3%83%A9%E3%83%A0%E3%82%92%E4%BD%9C%E3%82%8B%E9%9A%9B%E3%81%AB%E3%80%81%E3%83%86%E3%82%B9%E3%83%88%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E4%BD%9C%E3%82%8B%E6%99%82%E3%81%AB%E6%B3%A8%E6%84%8F%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8)を参考に


# buildとcreateの違いを教えて

- build()メソッドはインスタンスをメモリ上にのみ記録する。

- create()メソッドはテストデータベースにも保存して、データを永続化させる。

http://tkymtk.hatenablog.com/entry/2013/12/15/214420


# FactoryBotで出来ることって何があるの？

記事はFactoryGirlに関する内容ですが、学べることが多いです。
https://tech.grooves.com/entry/2015/04/28/173025

あとは[公式ドキュメント](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md)を読んでみる


# インスタンスメソッドをテストしたい

例えば、userの姓と名を連結するメソッド`name`があるとする。

```ruby:app/models/user.rb
def name
  [firstname, lastname].join(' ')
end
```

これをテストしてみる。

```ruby:spec/models/user_spec.rb
it "returns a user's full name as a string" do
  user = User.new(
    first_name: "John",
    last_name:  "Doe",
    email:      "johndoe@example.com",
  )
  expect(user.name).to eq "John Doe"
end
```

※Rspecで等値のExpectationを書く時は、 `==`ではなく、`eq`を使う。


# スコープをテストしたい

Noteモデルに、渡された文字列でメモ（note)を検索する機能を付与。

```ruby:app/models/note.rb
scope :search, ->(term) { 
  where("LOWER(message) LIKE ?", "%#{term.downcase}%")
}
```


```ruby:spec/models/note_spec.rb
require 'rails_helper'

Rspec.describe Note, type: :model do
  before do
    @user = User.create(
      first_name: "John",
      last_name:  "Doe",
      email:      "johndoe@example.com",
      password:   "password",
    )

    @project = @user.projects.create(
      name: "Test Project",
    )
  end

  # ユーザー、プロジェクト、メッセージがあれば有効な状態であること
  it "is valid with a user, project, and message" do
    note = Note.new(
      message: "This is a sample note.",
      user: @user,
      project: @project,
    )
    expect(note).to be_valid
  end

  # メッセージがなければ無効な状態であること
  it "is invalid without a message" do
    note = Note.new(message: nil)
    note.valid?
    expect(note.errors[:message]).to include("can't be blank")
  end

  # 文字列に一致するメッセージを検索する
  describe "search message for a team" do
    before do
      @note1 = @project.notes.create(
        message: "this is the first note.",
        user: @user,
      )
      @note2 = @project.notes.create(
        message: "this is the second note.",
        user: @user,
      )
      @note3 = @project.notes.create(
        message: "First, preheat the oven",
        user: @user,
      )
    end
     
    # 一致するデータが見つかったとき
    context "when a match is found" do
      # 検索文字列に一致するメモを返すこと
      it "returns notes that match the search term" do
        expect(Note.search("first")).to include(@note1, @note3)
      end
    end

    # 一致するデータが1件も見つからないとき
    context "when no match is found" do
      # 空のコレクションを返すこと
      it "returns an empty collection" do
        expect(Note.search("message")).to be_empty
      end
    end
  end
end
```

# FactoryBotで同時に複数個のインスタンスを生成したい

生成系のメソッドのそれぞれに対応する`*_list()`メソッドで、同じデータを複数件生成できる。
`build_list(), build_stubbed_list(), create_list(), attributes_for_list()`

```ruby:例
users = build_list(:user, 5, name: )
```