RSpec使い方まとめ

## Let メソッド
各example共通の変数を作れるらしい
``` let(:変数名){変数の値} ```という感じで書く
なおletで作った変数は、各exampleの内部で値を変えても
別のexampleの内部には影響しない

```ruby
RSpec.describe Card do
  let(:card){
    Card.new("Ace","Spades")
  }
# letメソッドを使って各example共有の変数cardを作る
# letブロックの内部は必要なときにのみ計算される
  it "has a rank and we can change it" do
    expect(card.rank).to eq("Ace")
    card.rank = "King"
    expect(card.rank).to eq("King")
  end
# 上記exampleでcard.rankを書き換えているが
# 他exampleの内部には影響はない
  it "has a suit" do
    expect(card.suit).to eq("Spades")
  end
end
```
## Original Error Message
```ruby
RSpec.describe Card do
  let(:card){
    Card.new("Ace","Spades")
  }
  it "has a rank and we can change it" do
    expect(card.rank).to eq("Ace")
    card.rank = "King!"
    expect(card.rank).to eq("King"), "doesn't have rank"
　　　　　　　　　　　　　　　　　　　　#toメソッドは第二引数で
　　　　　　　　　　　　　　　　　　　　#オリジナルエラーメッセージを作れる
  end
  it "has a suit" do
    expect(card.suit).to eq("Spades"), "doesn't have suit"
  end
end
```

## Nested describe and context
``` it ```のあとに長々と文章を書くのは宜しくない、
たとえば``` it should return true if the number is even ```みたいなのは
見づらいという事で、``` RSpec.describe ```の内部で``` context ```を使って
条件節ごとに区切るのがお作法らしい
なお``` context ```の挙動は``` describe ```とおんなじとのこと

```ruby
RSpec.describe "#even? method" do
  let(:num){4}
  context "with even number" do
    it "should return true" do
#itのあとはshould+verbだけを書くのだ！
      expect(num.even?).to be(true),"#{num} isn't an even number"
    end
  end
  context "with odd number" do
    it "should return true" do
      expect(num.odd?).to be(false),"#{num} isn't an even number"
    end
  end
end
```
##Before and After hooks
example(it)やcontextの前に実行するコードを仕分けできる

```ruby
  before(:context) do
#これはcontextの前に実行
    puts "hi, I'm context"
  end
  before(:example) do
#:exampleにするとitの前に実行
    puts "hi, I'm example"
  end
```
テストの前後にデータベースへの接続／切断が必要なときなどに使うらしい
（まったくイメージが沸かない）

面白いのは``` before(:context) ```と``` before(:example) ```の実行タイミング
``` before(:context) ```は自らを含むブロックの直前でのみ実行される
一方``` before(:example) ```は、自分とネストの深さが同じ、もしくはより深いブロックの
exampleの前でもれなく実行される

```ruby
#ここでOUTER before contextが実行される
RSpec.describe "nested hooks" do

 before(:context) do
  puts "OUTER before context"
 end

 before(:example) do
  puts "OUTER before example"
 end

#ここでOUTER before exampleが実行される
 it "does basic math" do
  expect(1 + 1).to eq(2)
 end

#ここでINNER before contextが実行される
 context "with condition A" do
  before(:context) do
    puts "INNER before context"
   end

   before(:example) do
    puts "INNER before example"
   end

#ここでOUTER before exampleが実行される
#ここでINNER before exampleが実行される
   it "does basic math" do
    expect(1 + 1).to eq(2)
   end

#ここでOUTER before exampleが実行される
#ここでINNER before exampleが実行される
   it "does basic subtraction" do
    expect(3 - 1).to eq(2)
   end
 end
end
```

## Subject

``` RSpec.describe ```の引数に文字列ではなくオブジェクトを取る場合、
そのインスタンスがブロック内で変数``` subject ```に格納される

```ruby
RSpec.describe Hash do
  it "should starts off empty" do
    expect(subject.length).to eq(0)
  end
end
```

以下のように、変数subjectにブロックの返り値を代入することも可能。
（これならletでいいじゃん……という気もするが……）

```ruby
RSpec.describe Hash do
  subject do
    { a: 0, b: 1}
  end
  it "should starts off empty" do
    expect(subject.length).to eq(0)
  end
end
```
```ruby
RSpec.describe Array do
#subject以外の名前のインスタンスも作成可能
  subject(:arr1) do
    Array.new([""])
  end

  it "should be a 1-length array" do
    expect(arr1.length).to eq(1)
  end
end
```
## Described Class

```ruby
class King
  attr_reader :name
  def initialize(name)
    @name = name
  end
end

RSpec.describe King do
  subject(:king) do
    described_class.new("Richard")
#class Kingを呼び出している
  end
  it "should have a name Richard" do
    expect(king.name).to eq("Richard")
  end
end
```
describeの引数として取ったクラスは、ブロック内でdescribed_classとして呼び出し可能。

##One-Liner-Expresshin for Subject
```ruby
RSpec.describe String do
  subject{"String"}
  it {is_expected.to eq("String")}
#is_expectedオブジェクトは、自動でsubjectを引数で取る。
#逆に言うとsubjectしか引数に取れない。
end
```
## Shared Example

``` RSpec.shared_examples ```ブロック内部のexampleを
``` include_examples ```内に挿入可能。
（いずれもexamplesが複数形なのに気を付けてね……）
今回も「何をマッチするか」はsubjectでしか取れない模様

```ruby
RSpec.shared_examples "is length 3?" do
  it "returns the number of items" do
    expect(subject.length).to eq(3)
  end
end

RSpec.describe Array do
  subject{[0,1,2]}
  include_examples "is length 3?"
end

RSpec.describe String do
  subject{"ABC"}
  include_examples "is length 3?"
end
```
## Matchers
```ruby
RSpec.describe do
  context "when 6" do
    subject{6}
    it {is_expected.to eq(6.0)}
#eqは「値」のみの等しさを見る
    it {is_expected.not_to eq(7)}
    it {is_expected.not_to eql(6.0)}
#eqlは「値」と「型」の等しさを見る
  end
  context "object identity" do
    let(:a){[1,2,3]}
    let(:b){[1,2,3]}
    let(:c){a}
    it "doesn't always be what is looks like" do
      expect(a).not_to equal(b)
#equalはオブジェクトの同一性を見る
      expect(a).to equal(c)
    end
  end
  describe 6 do
#beの引数にはcomperisonが取れる
    it{is_expected.to be > 5}
    it{is_expected.to be < 7}
    it{is_expected.to be >= 6}
  end

  context "test" do
    it "test" do
      expect({}).to be_empty
#allで配列の要素すべてがmatchするか見れる
      expect([10,20,30]).to all(be_even)
      expect([0, 1, 2]).to all(be >= 0)
      expect(0).to be_zero
    end
  end
end

RSpec.describe "change matcher" do
  subject {[]}
  it "should change from 0 to 1" do
#expectに続くブロック内で行った操作による変化をチェックできる
    expect{subject.push("")}.to change {subject.length} .from(0).to(1)
#byを使えば値の変化量だけを見れる
    expect{subject.push("")}.to change {subject.length} .by(1)
  end
end

RSpec.describe Array do
  subject{[1,2,3]}
#includeは「含むか」
  it {is_expected.to include(1,2)}
#contail_exactlyは「完全に含むか」
  it {is_expected.to contain_exactly(1,2,3)}
end

RSpec.describe "DOG" do
  it{is_expected.to start_with("D")}
  it{is_expected.to end_with("G")}
end

describe [1,2,3,4] do
  it{is_expected.to start_with(1,2)}
end

class King
  attr_reader :name, :nation
  def initialize(name,nation)
    @name = name
    @nation = nation
  end
end

describe King do
  subject{King.new("John","ENG")}
#プロパティが一定の値を持つか
  it{is_expected.to have_attributes(name:"John")}
end

describe ({a: 1, b: 2}) do
#プロパティを持つかどうかはincludeが有用
  it{is_expected.to include(:a)}
end


RSpec.describe "raise error matcher" do
  it "should raise error" do
    expect{undefined}.to raise_error(NameError)
    expect {1 / 0}.to raise_error(ZeroDivisionError)
  end
end

class Test
  def test01
    "wow"
  end
  def test02(num)
    "yeah" * num
  end
end

RSpec.describe Test do
#メソッドを持つか
  it{is_expected.to respond_to(:test01)}
#メソッドが引数を取るか
  it{is_expected.to respond_to(:test02).with(1).arguments}
end

#satisfyを使うと続くブロックの中でprogramaticallyに引数をチェック可能
RSpec.describe "asdsa" do
  it{is_expected.to satisfy("be a palindrome") do |word|
    word == word.reverse
  end
}
end

RSpec.describe 6 do
#andやorメソッドでmatcherを組み合わせられる
#なぜかここではsatisfyは使えない
  it{is_expected.to be_even.and be > 5}
end
```
## Allow Method
```ruby
RSpec.describe "allow method" do
  it "#allow method" do
    arr = [1,2,3]
#sumメソッドの挙動を変えている
    allow(arr).to receive(:sum).and_return(10)
    expect(arr.sum).to eq(10) 
  end
end

RSpec.describe "mimic array" do
  it "mimics the behavior of array" do
    mock_array = double
#and_returnの引数を複数取ることで、
#popメソッドの呼び出し回数ごとの返り値を変えられる
#popメソッドのたびに3,2,nilの順に戻る（そのあとはずっとnil)
    allow(mock_array).to receive(:pop).and_return(3,2,nil)
    expect(mock_array.pop).to eq(3)
    expect(mock_array.pop).to eq(2)
    expect(mock_array.pop).to eq(nil)
    expect(mock_array.pop).to eq(nil)
    expect(mock_array.pop).to eq(nil)
  end
  it "mimics the behavior of first" do
    mock_num = double
    allow(mock_num).to receive(:first).and_return(1)
#withによって引数で挙動を変えることができる
    allow(mock_num).to receive(:first).with(1).and_return([])
    expect(mock_num.first).to eq(1)
    expect(mock_num.first(1)).to eq([])
  end
end
```

## Instance double
```ruby
class Person
  def a
  end
  def b
  end
end

RSpec.describe Person do
  it "will mimic Person instance" do
#instance_doubleは、「元ネタ」となるクラスにない
#プロパティを取るとエラーを投げてくれる
    stuntman = instance_double(Person,a:20,c:30)
  end
  it "will mimic Person instance with argument" do
    stuntman = instance_double(Person,a:20,b:30)
#「元ネタ」では引数を取らないメソッドが引数を取ってもエラーを返す
    allow(stuntman).to receive(:a).with(3).and_return("")
  end
end
```
# Class Double
```ruby
class CardGame
  def start
#存在しないクラス「Deck」を参照するメソッド
    @card = Deck.build
  end
  def show
    @card
  end
end

RSpec.describe CardGame do
  it "test" do
#class_doubleの引数で名前とメソッドを定義し、.as_stubbed_constを後につけると
#ブロック内で存在しないDeckが存在するように振舞う
    deck_class = class_double("Deck",build: ["Ace","Queen"]).as_stubbed_const
    subject.start
    expect(subject.show).to eq(["Ace","Queen"])
  end
end
```