RSpecを綺麗に書くための基本Rule



# ○概要

一つのロジックを変えると、100個くらいのspecエラーが発生することも有ります。
specの消化活動をしていくうちに、継ぎ接ぎだらけのテストになってしまう場合もあります。
そこで、今回は継ぎ接ぎだらけのRSpecにしないための統一ルールをまとめました。
(経験も浅いので、どんどん更新していきます。)


# ○理想的なテスト　
##### *テストの理想 = MECE + Readable + Flexible*


##### *MECE*
テストの対象に対して、必要十分なテストケースを用意するべきです。 

##### *Readable*
他のspecファイルと違う変数名を入れないようにしましょう。
短くて適当よりも、長くて正確のほうが良いです。

##### *Flexible*

テストデータが深くネストをしていたり、
before :eachでくくりすぎという問題が、specの修正を困難にします。


# ○命名規則

「AAメソッドは、BBの時、CCをDDします。」
## AA : Describe
コントローラーのメソッド名を表記

ex) 

```rb
describe "GET #show"
```

## BB : Context

- 状況を表記  
- when/withで始める

ex) 

```rb
context "when book is present"
context "with valid params"
```

## CC : Subject

- テスト対象を表記

ex)
 
```rb
# eqの場合
subject{ assings(:book) }
subject{ Book.find(1)}

# change/render/redirect/http statusの場合
subject {Proc.new { get: index }}
subject {Proc.new { get :show, id: 1 }}
subject {Proc.new { get :new, id: 1 }}
subject {Proc.new { get :edit, id: 1 }}
subject {Proc.new { post :create, id: 1, book: @valid_params }}
subject {Proc.new { patch :update, id: 1, book: @valid_params }}
subject {Proc.new { destroy :delete, id: 1 }}

```

## Dd : it

- テスト対象に対する動詞を表記

ex)


```rb

# eqの場合
it "主語 is 目的語"
it "主語 has 目的語"

# changeの場合

it "create 目的語"
it "update 目的語"
it "delete 目的語"

# renderの場合
it "render the template名"

# redirectの場合
it "redirect to path名"

# http statusの場合
it "returns http ok"
it "returns http success"
it "returns http 204"
```


# ○設計規則
①describe > context > subject > it 
subjectとshared_exampleを使うとかなりdryになるし、柔軟性も高い。

②基本的にbefore :eachを使用しない。
testは独立していたほうが、修正しやすいし読みやすい。
let! と letを使い分けたほうが操作性が楽。
let!も多様しない。テストデータを修正するときに影響範囲が大きい


# ○マッチャand命名規則
基本的には、以下の4つを各controllerのmethod毎に使用します。

- eq系(変数取得)
- change系(モデルの増減)
- render/redirects系
- http status系  

以上の4種類を使います。

## * eq系(変数取得)

### [概要]
値の整合性を確認します。

### [マッチャ例]

```rb
expect( subject ).to eq @book 
expect( subject ).not_to eq @book 
```


## * change系(モデルの増減)

### [概要]
モデルのレコードの増減を確認します。
目的語は正確に書きます。
なるべく、change.from().to()を使いましょう。

### [マッチャ例]

増

```rb
 expect{ subject.call }.to change(Book, :count).from(0).to(1)
 expect{ subject.call }.to change(Book, :count).by(1)
```

減

```rb
 expect{ subject.call }.to change(Book, :count).from(1).to(0)
 expect{ subject.call }.to change(Book, :count).by(-1)
```

不変

```rb
 expect{ subject.call }.to change(Book, :count).from(0).to(0)
 expect{ subject.call }.to change(Book, :count).by(0)
```



## * render/redirect系
### [概要] 
render template/ redirect_to を確認します。


### [マッチャ例]

render_template

```rb
subject.call
expect(response).to render_template :show
```

redirect_to

```rb
subject.call
expect(response).to redirect_to(book_path)
```

## * http status系

### [概要]
http statusが正常かどうかを確認します。


### [マッチャ例]
status code == 200 の場合　

```rb
subject.call
expect(response).to have_http_status(:ok)
```

status code ==2xx の場合

```rb
subject.call
expect(response).to have_http_status(:success) 
```

status code == 204　の場合

```rb
subject.call
expect(response).to have_http_status(204) 
```

他にも書き方はありますが、紛らわしいので上で統一します。



# ○トラブルシューティング

## インデントに気をつける。
事実かどうかは怪しいが、インデントが揃ってないとdbクリーナーが正常に働かない事がある。
全体ファイルでテストした時に、エラーが出る可能性がある。

## letの変数と、ローカル変数の名前を一緒にしない。
競合してると、エラーが出る可能性がある。
単体ファイルよりも、全体ファイルでテスト回すとエラーが出る可能性がある。


# ○RSpecベストプラクティス


```rb
require "rails_helper"

describe BooksController do
  # http stasus case
  shared_examples_for "returns http success" do
    it { subject.call; expect(response).to have_http_status(:success)}
  end

  # render template case
  shared_examples_for "render template" do |template|
    it { subject.call; expect(response).to render_template template}
  end
  
  # redirect to path case
  shared_examples_for "redirect to path" do |path|
    it { subject.call; expect(response).to render_to path}
  end

  # create/update/delete model case
  shared_examples_for "create Book" do |model|
    it { expect{subject.call}.to change{model.count}.by(1) }
  end

  shared_examples_for "update Model" do |model|
    it { expect{subject.call}.to change{model.count}.by(0) }
  end

  shared_examples_for "delete Model" do |model|
    it { expect{subject.call}.to change{model.count}.by(-1) }
  end
  
  # eq case
  shared_examples_for "assinged value is @value" do |value|
    subject.call
    it { expect(value).to eq value }
  end

  let(:author) { create(:author)}
  let(:book) { create(:book, author: author) }
  let(:valid_params) { attributes_for(:book, author: author) }
  let(:invalid_params) { attributes_for(:book, author: nil) }

  describe "GET #show" do
    subject {Proc.new { get :show, id: 1 }}
    before { @value = book}
    it_behaves_like "returns http success"
    it_behaves_like "assinged value is value" :book
  end

  describe "POST #create" do
    context "with valid params" do
      subject {Proc.new { post :create, id: 1, book: valid_params }}
      it_behaves_like "returns http success"
      it_behaves_like "create Model" Book
      it_behaves_like "redirect to path" root_path
    end

    context "with invalid params" do
      subject {Proc.new { post :create, id: 1, book: invalid_params }}
      it ~~~~
      # ここも同じ要領でshared_exampleで作成しても良いですし、直接書いてもいいです。
    end
end

```