Let メソッド
各example共通の変数を作れるらしい
let(:変数名){変数の値}
という感じで書く
なおletで作った変数は、各exampleの内部で値を変えても
別のexampleの内部には影響しない
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
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
とおんなじとのこと
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の前に実行するコードを仕分けできる
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の前でもれなく実行される
#ここで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
に格納される
RSpec.describe Hash do
it "should starts off empty" do
expect(subject.length).to eq(0)
end
end
以下のように、変数subjectにブロックの返り値を代入することも可能。
(これならletでいいじゃん……という気もするが……)
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
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
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
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でしか取れない模様
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
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
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
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
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