0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RSpec使い方まとめ

Last updated at Posted at 2022-09-19

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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?