RailsでのRSpecの設定やら書き方とかいろいろ

#初めに
Ruby on Railsのテストにはmini testではなくrspecが用いられることが多いようで実際に会社で働こうと思ったらrpsecについて知っておかなくてはいけないと思い勉強したことの備忘録。

いろいろ間違っているかも

#設定

とりあえずgemfileに

```
gem 'rspec-rails'
```

と書いてbundle installして

```
rails g rspec:install
```

を行えばrspecに関係するファイルが作られる。

config/application.rbに

```ruby 
config.generators do |g|
  g.test_framework :rspec
end
```

と書くとモデルやらコントローラーやらを作成したときにそれに関係するrspecのファイルを作ってくれる。

逆にrspecのファイルを自動的に作ってほしくないときは

```ruby
config.generators do |g|
  g.test_framework false
end
```

とかいたらok

controllerに関するrpsecのファイルは作成しなくてよいけど他のは作ってほしいみたいなときは

```ruby
config.generators do |g|
  g.test_framework :rspec,
  controller_specs: false
end 
```

みたいに設定できる

rspecファイルを作りたい場合は

```
rails g rspec:model user
```

みたいな感じで書く。この場合はuser のmodelに対するrspecのファイルを作成する。

ほかにも

```
rails g rspec:job users
rails g rspec:view users
rails g rspec:helper users
rails g rspec:feature users
```

みたいなかんじでできる。



ルートディレクトリに.rspecというファイルが作成されているがそこでrspecの出力に関する設定ができる。

とりあえず

```
--require spec_helper
--color
--format d
```

こうしたらいい感じになる。


rspecの実行に関しては

```
rspec
もしくは
bundle exec rspec
```

でいけるとおもう。


#具体的になにをするか先に書く

こんな感じでなにをするかさきに書く

```ruby:user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do

  it "名前とメールアドレスとパスワードがあれば登録できる"

  it "名前がなければ登録できない"

  it "メールアドレスがなければ登録できない"

  it "メールアドレスが重複していたら登録できない"

  it "パスワードがなければ登録できない"

  it "パスワードが暗号化されているか"

  
end
```

この状態で実行すると

```
User
  名前とメールアドレスとパスワードがあれば登録できる (PENDING: Not yet implemented)
  名前がなければ登録できない (PENDING: Not yet implemented)
  メールアドレスがなければ登録できない (PENDING: Not yet implemented)
  メールアドレスが重複していたら登録できない (PENDING: Not yet implemented)
  パスワードがなければ登録できない (PENDING: Not yet implemented)
  パスワードが暗号化されているか (PENDING: Not yet implemented)

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) User 名前とメールアドレスとパスワードがあれば登録できる
     # Not yet implemented
     # ./spec/models/user_spec.rb:5

  2) User 名前がなければ登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:7

  3) User メールアドレスがなければ登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:9

  4) User メールアドレスが重複していたら登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:11

  5) User パスワードがなければ登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:13

  6) User パスワードが暗号化されているか
     # Not yet implemented
     # ./spec/models/user_spec.rb:15


Finished in 0.00456 seconds (files took 0.95882 seconds to load)
6 examples, 0 failures, 6 pending
```

みたいに出る。

これので一つ一つテストを成功させていく。


#データ作成をfactory_bot_railsで行う。

テストに使うデータの作成にfactory_bot_railsというgemを使う

とりあえず

```
gem 'factory_bot_rails'
```

bundleする。

spec/rails_helper.rbに

```
config.include FactoryBot::Syntax::Methods
```

と書いておく

コマンドで

```
rails g factory_bot:model user
```

と書けば必要なファイルが作成される


factories/users.rbに

```users.rb
FactoryBot.define do 
    factory :user do 
        name {"hiro"}
        sequence(:email) { |n| "hiro#{n}@example.com"}
        password {"password"}
    end 
end 
```

こんな感じで書いておくと

```
user = FactoryBot.create(:user)
```

でデータを作成してくれる

なぜemail {"hiro@example.com"}ではないかというと

emailは一意制約を付けているので

```ruby 
user1 = FactoryBot.create(:user)
user2 = FactoryBot.create(:user)
```

とするとエラーが出てしまう

```ruby
sequence(:email) { |n| "hiro#{n}@example.com"}
```

とかけばエラーが避けられる

spec/models/user_spec.rbに具体的な処理を書く


```ruby 
require 'rails_helper'

RSpec.describe User, type: :model do

  it "名前とメールアドレスとパスワードがあれば登録できる" do 
    expect(FactoryBot.create(:user)).to be_valid
  end 

  it "名前がなければ登録できない" do 
    expect(FactoryBot.build(:user, name: "")).to_not be_valid 
  end 

  it "メールアドレスがなければ登録できない" do 
    expect(FactoryBot.build(:user, email: "")).to_not be_valid 
  end 

  it "メールアドレスが重複していたら登録できない" do 
    user1 = FactoryBot.create(:user,name: "taro", email: "taro@example.com")
    expect(FactoryBot.build(:user, name: "ziro", email: user1.email)).to_not be_valid
  end 

  it "パスワードがなければ登録できない" do 
    expect(FactoryBot.build(:user, password: "")).to_not be_valid 
  end 

  it "パスワードが暗号化されているか" do 
    user = FactoryBot.create(:user)
    expect(user.password_digest).to_not eq "password"
  end 

  it "password_confirmationとpasswordが異なる場合保存できない" do 
    expect(FactoryBot.build(:user,password:"password",password_confirmation: "passward")).to_not be_valid 
  end 


end
```


とりあえずbundle exec rspecをすると....



```
User
  名前とメールアドレスとパスワードがあれば登録できる (FAILED - 1)
  名前がなければ登録できない (FAILED - 2)
  メールアドレスがなければ登録できない (FAILED - 3)
  メールアドレスが重複していたら登録できない (FAILED - 4)
  パスワードがなければ登録できない (FAILED - 5)
  パスワードが暗号化されているか (FAILED - 6)

Failures:

  1) User 名前とメールアドレスとパスワードがあれば登録できる
     Failure/Error: expect(FactoryBot.create(:user)).to be_valid

     NoMethodError:
       undefined method `password=' for #<User:0x00007fffbd4d20f0>
       Did you mean?  password_digest=
     # ./spec/models/user_spec.rb:6:in `block (2 levels) in <top (required)>'

  2) User 名前がなければ登録できない
     Failure/Error: expect(FactoryBot.create(:user, name: "")).to_not be_valid

     NoMethodError:
       undefined method `password=' for #<User:0x00007fffbd5462c0>
       Did you mean?  password_digest=
     # ./spec/models/user_spec.rb:10:in `block (2 levels) in <top (required)>'

  3) User メールアドレスがなければ登録できない
     Failure/Error: expect(FactoryBot.create(:user, email: "")).to_not be_valid

     NoMethodError:
       undefined method `password=' for #<User:0x00007fffbd54d070>
       Did you mean?  password_digest=
     # ./spec/models/user_spec.rb:14:in `block (2 levels) in <top (required)>'

  4) User メールアドレスが重複していたら登録できない
     Failure/Error: user1 = FactoryBot.create(:user,name: "taro", email: "taro@example.com")

     NoMethodError:
       undefined method `password=' for #<User:0x00007fffbc254b08>
       Did you mean?  password_digest=
     # ./spec/models/user_spec.rb:18:in `block (2 levels) in <top (required)>'

  5) User パスワードがなければ登録できない
     Failure/Error: expect(FactoryBot.create(:user, password: "")).to_not be_valid

     NoMethodError:
       undefined method `password=' for #<User:0x00007fffbd56fa80>
       Did you mean?  password_digest=
     # ./spec/models/user_spec.rb:23:in `block (2 levels) in <top (required)>'

  6) User パスワードが暗号化されているか
     Failure/Error: user = FactoryBot.create(:user)

     NoMethodError:
       undefined method `password=' for #<User:0x00007fffbd57b6a0>
       Did you mean?  password_digest=
     # ./spec/models/user_spec.rb:27:in `block (2 levels) in <top (required)>'

Finished in 0.01216 seconds (files took 0.9921 seconds to load)
6 examples, 6 failures

Failed examples:

rspec ./spec/models/user_spec.rb:5 # User 名前とメールアドレスとパスワードがあれば登録できる
rspec ./spec/models/user_spec.rb:9 # User 名前がなければ登録できない
rspec ./spec/models/user_spec.rb:13 # User メールアドレスがなければ登録できない
rspec ./spec/models/user_spec.rb:17 # User メールアドレスが重複していたら登録できない
rspec ./spec/models/user_spec.rb:22 # User パスワードがなければ登録できない
rspec ./spec/models/user_spec.rb:26 # User パスワードが暗号化されているか

```


こんな感じでエラーがでる。

モデルファイルを修正してエラーが出ないようにする

models/user.rbに


```
class User < ApplicationRecord
    has_secure_password

    validates :name, presence: true
    validates :password, presence: true
    validates :email, presence: true 
    validates :email, uniqueness: true
end
```

こう書けばエラーが消えで全部緑で表示される:sunny:


基本的にexpect()のところに検証したいものを入れて

そのあとにbe_validであれば有効か無効かを検証したり
eqであれば等しいかどうかを検証する。

expect().toのところをto_notみたいにもできる


#FactoryBot.create FactoryBot.buildの違い

buildはデータを実際に保存する必要がない場合や属性の検証のときに使う。
今回の場合は全部buildで大丈夫

#let
letを使えば何回も同じコードを書かないですむ
例えば

```ruby
let(:user) { FactoryBot.build(:user) }
```

とすればそのあとは
userと書くだけで{ FactoryBot.build(:user) }が実行されてデータを作ってくれる。

#let!

let!にするとその場でテストデータを作ってくれる。
letの場合はそれが呼び出される時にコードを実行する

#before

beforeはテストが実行される前に行われるもので基本的なデータをbeforeで作ったりどのテストでも行われているログイン処理とかをかいてテストを簡潔にできる。

書き方は

```
before do
  @user = User.new(name: "test", email: "test@gmail.com", password: "password")
　@user.save
end
```

みたいな感じ。


個人的にモデル作成に関してはletを使えばいいかなと思う
ログイン処理とかに関してはbeforeを使うってかんじなのかな？わからないけど....

#FeatureSpec と System Spec
昔はFeatureSpecが使われてて今っぽいのがSystemSpecらしい

具体的に何をするものかというと

例えばユーザー登録画面を開いて入力欄に文字をいれて登録ボタンをおしてユーザー登録が成功したか


みたいなのを検証するものがsystem specでそういったテストを統合テストとよぶ

systemspecを行おうまえにseleliumwebdriverやらchromedriverやらの設定が必要

それに関してはこちらを参考にしてください

EC2 UbuntuでGoogle Chromeをヘッドレス実行してスクリーンショットを採取する手順
https://qiita.com/shinsaka/items/37436e256c813d277d6d



ページにアクセスする場合は

```
visit root_path
```

みたいに書いて

入力は

```
fill_in "Name", with: "Hiroto"
fill_in "Email", with: "hiroto@gmail.com"
```

みたいな感じ

ボタンクリックは

```
click_button "Create user"
```

見たいな感じ

たしか<%= f.label :*** %>のところに書いてある文字をfill_inで指定してwithで入力する文字を指定する感じ

id="***"で指定することもできる



検証の仕方は

```
expect(page).to have_content "hello world"
```

で訪れたページにhello worldという文字があるか検証できる


ユーザー登録ができているか確認する場合はこんな感じ


```ruby:users_spec.rb
require 'rails_helper'

describe 'ユーザー登録', type: :system do 

    before do
        driven_by :selenium_chrome_headless
    end

    it "ユーザー登録ができるか" do

        expect {
        visit new_user_url
        fill_in "Name", with: "yuki"
        fill_in "Email", with: "yuki@gmail.com"
        fill_in "Password", with: "password"
        fill_in "Password confirmation", with: "password"
        click_button "submit"
        expect(page).to have_content "index"
        }.to change(User,:count).by(1)
    end 


end 
```

ここで重要なのがexpect {}の中にexpect(page).to have_content "index"と書いている

これを書かないとclick_buttonが押された後処理が実行される前にchange(User,:count).by(1)を検証してしまうためテストが失敗する。

大体こんな感じだと思う。

feature specを使ったときはitのところにscinarioと書いたりdatabase_cleanerをgem installしたりしたけどsystem specの場合はいらないみたい。


なにか間違っているところがあればご指摘いただけるとありがたいです。