【RSpec】基本的な書き方

#はじめに
この記事は、[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612)を参考（引用）してます。

#describe / it / expect の役割
RSpecでよく使うdescribe, it, expectについて。
##describe テストのグループ化
・ describeはテストのグループ化を宣言する。
・ describeの中では複数の*example*(`it end ... do`)を記述できる。

##it テストをexampleにまとめる
・ itはテストを*example*という単位にまとめる役割をする。
・ `it do ... end`の中のエクスペクテーション（期待値と実際の値の比較）が全てパスすればその*example*はパスしたことになる。
・ `it do ... end`のなかには一つのエクスペクテーション（`expect`）を描く。（原則なので破っても良い）

##expect エクスペクテーション
itの部分で述べたエクスペクテーションがexpectにあたり、
`expect(X).to eq Y`のような形で描く。

#使えると便利なcontext / before
###context
contextは条件でグループ化するときに使う。

```ruby
RSpec.describe User do
  describe '#greet' do
    context '12歳以下の場合' do
      it 'ひらがなで答えること' do
        user = User.new(name: 'たろう', age: 12)
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      it '漢字で答えること' do
        user = User.new(name: 'たろう', age: 13)
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
```

###before
・ beforeは共通の前準備をする時に使える。
・ `before do ... end`に記述したものは、
　*example*(`it do ... end`)の実行前に呼ばれる。
・ beforeはテスト実行前の共通処理やデータのセットアップ等を行うことが多い。

```ruby
RSpec.describe User do
  describe '#greet' do
    before do
      @params = { name: 'たろう' }
    end
    context '12歳以下の場合' do
      it 'ひらがなで答えること' do
        user = User.new(@params.merge(age: 12))
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      it '漢字で答えること' do
        user = User.new(@params.merge(age: 13))
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
```

beforeではインスタンス変数を使用している。
これは`before`と`it`では変数のスコープが異なるため。
  
####ネストしたdescribe, contextの中でのbefore
beforeは`describe`や`context`ごとに用意できる。
呼ばれる順番は、親、子の順。

```ruby
RSpec.describe User do
  describe '#greet' do
    before do
      @params = { name: 'たろう' }
    end
    context '12歳以下の場合' do
      before do
        @params.merge!(age: 12)
      end
      it 'ひらがなで答えること' do
        user = User.new(@params)
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      before do
        @params.merge!(age: 13)
      end
      it '漢字で答えること' do
        user = User.new(@params)
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
```

#letの役割
###インスタンス変数の代わり
これまでは`before`ブロックに`@params`という形でインスタンス変数を利用してきました。これを`let`を使って次のように書き換えられる。

```ruby
#これを
  before do
    @params = { name: "たろう" }
  end
#このように
  let(:params) { { name: "たろう" } }
```

###ローカル変数の代わりにも
インスタンス変数だけでなく、ローカル変数も`let`でかける。

```ruby
#これを
  user = User.new(params)
#このように
  let(:user) = User.new(params)
```

###letのメリット
`before`が*expample*(`it do ... end`)の前に呼ばれるのに対し、`let`は遅延評価される。`let`は必要になるまで呼ばれない。

```ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(params) }
    let(:params) { { name: 'たろう', age: age } }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it 'ひらがなで答えること' do
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it '漢字で答えること' do
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
```

ここでは、
1. `expect(user.greet).to eq`の部分で`user`をさがす。
2. `let(:user) { User.new(params) }`が呼ばれ、
　 ここで`params`をさがす。
3. `let(:params) { { name: 'たろう', age: age } }が`呼ばれ、次は`age`をさがす。
4. `let(:age) { 12 }`がよばれる。 

のようにして`let`が呼び出される。

###事前に実行されるlet!
`let!`を使うとexample*の実行前に`let!`で定義した値が作られる。

```ruby
RSpec.describe Blog do
  #事前にblogが作られるためexpectでエラーにならない。
  let!(:blog) { Blog.create(title: 'RSpec必勝法', content: 'あとで書く') }
  it 'ブログの取得ができること' do
    expect(Blog.first).to eq blog
  end
end
```

#subjectの使い方
`subject`を使えば同じエクスペクテーションを一つにまとめることができる。

```ruby
#12歳以下のとき
expect(user.greet).to eq 'ぼくはたろうだよ。'
#13歳以上のとき
expect(user.greet).to eq '僕はたろうです。'

#上の二つを次のようにまとめる
subject { user.greet } #subjectで宣言し、
is_expected.to eq 'ぼくはたろうだよ。' #is_expected.toでテスト
```
  
さらに、`it`を省略して、次のようにかける。

```ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(name: 'たろう', age: age) }
    subject { user.greet }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it { is_expected.to eq '僕はたろうです。' }
    end
  end
end
```

#itのエイリアス specify, example
`it`の代わりに`specify`と`example`が使える。

```ruby
# それ(it)はユーザー名を返す
it 'returns user name' do
  # ...
end

# 会社は社員を持つことを仕様として明記(specify)する
specify 'Company has employees' do
  # ...
end

# fizz_buzzメソッドの実行例(example)
example '#fizz_buzz' do
  # ...
end
```

#RSpecの高度な機能
### shared_example と it_behaves_like
`shared_example` と `it_behaves_like`を使えば、`example`の再利用ができる。　

```ruby
RSpec.describe User do
  describe '#greet' do
    
    #省略

    shared_examples '大人のあいさつ' do
      it { is_expected.to eq '僕はたろうです。' }
    end
    context '13歳の場合' do
      let(:age) { 13 }
      it_behaves_like '大人のあいさつ'
    end
    context '100歳の場合' do
      let(:age) { 100 }
      it_behaves_like '大人のあいさつ'
    end
  end
end
```

`shared_example "foo" do ... end`で再利用したい`example`を定義し、`it_behaves_like "foo"`で呼び出す。

###shared_context と include_context 
`shared_example`と`it_behaves_like`のように、
`context`の再利用には、`shared_context`と`include_context`が使える。

```ruby
RSpec.describe User do
  let(:user) { User.new(name: 'たろう', age: age) }

　#これらの記述でlet(:age)の繰り返しをなくす
  shared_context '12歳の場合' do
    let(:age) { 12 }
  end
  shared_context '13歳の場合' do
    let(:age) { 13 }
  end

  describe '#greet' do
    subject { user.greet }
    context '12歳以下の場合' do
      include_context '12歳の場合'
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end
    context '13歳以上の場合' do
      include_context '13歳の場合'
      it { is_expected.to eq '僕はたろうです。' }
    end
  end

  describe '#child?' do
    subject { user.child? }
    context '12歳以下の場合' do
      include_context '12歳の場合'
      it { is_expected.to eq true }
    end
    context '13歳以上の場合' do
      include_context '13歳の場合'
      it { is_expected.to eq false }
    end
  end
end
```

`shared_context "foo" do ... end`で再利用したいコンテキスト宣言し、*context*の中で`include_context "foo"`を使って呼び出す。

#スキップさせたいとき
RSpecではテストを一旦置いときたいときにスキップできる。

### xを使う
`it`や`describe`の前に`x`と書き`xit`, `xdescribe`とすることで、そのテストをスキップできる。(pending扱いになる。)

```ruby
RSpec.describe '何らかの理由で実行したくないクラス' do
  #xexample, xspecify も使える。
  xit '実行したくないテスト' do
    expect(foo).to eq bar
  end
end

#describeをスキップ
xdescribe '四則演算' do
  
end

# contextをスキップ
xcontext '管理者の場合' do
  
end
```

### テストは後で書く、中身のない it
`it "foo" do ... end`の`do ... end`を省略することでpendingにすることができる。

```ruby
RSpec.describe User do
  describe '#good_bye' do
    context '12歳以下の場合' do
      #do ... end を省略
      it 'ひらがなでさよならすること'
    end
end
```