17
14

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 5 years have passed since last update.

RSpecAdvent Calendar 2016

Day 7

RSpecの小技集

Posted at

この投稿は RSpec Advent Calendar 2016 (幻の)7日目の記事です。

自分用メモとして投稿しようと思ったのですが、ちょうど今日のAdvent Calendarが空いていたので飛び入り参加させていただきました。(そういう意味で 幻の7日目 です )


RSpecを書いていてよく「〇〇したいんだけど、どうやるんだっけ?」とググることが多かったのでまとめてみました。
RSpecの基本文法を理解している中級者以上向けの内容と思います。

RSpecは3.4を使っています。(ちょっと古くてすみません)
前提として以下のクラスがあるとします。
サンプルコードに出てくる foo という変数はこのFooクラスのインスタンスです。

class Foo
  def bar
    1
  end

  def baz(arg)
    2
  end
end

複数インスタンスのメソッドが呼ばれる度に異なる値を返す

クラスFooの複数インスタンスでメソッドbarが呼ばれる度に異なる値を返したい場合のやり方です。

まず、単一インスタンスであれば

allow(foo).to receive(:bar).and_return(:a, :b, :c)

でOKですが、これを単純に

allow_any_instance_of(Foo).to receive(:bar).and_return(:a, :b, :c)

expect(Foo.new.bar).to eq :a
# -> OK
expect(Foo.new.bar).to eq :b
# -> Failure

としても期待通りには動きません。

そこで以下のようにします。

values = [:a, :b, :c]
allow_any_instance_of(Foo).to receive(:bar) { values.shift }

expect(Foo.new.bar).to eq :a
# -> OK
expect(Foo.new.bar).to eq :b
# -> OK

ブロック/Procで渡せばbarが呼ばれる都度評価されるので、配列の先頭から取り出されていきます。

メソッドの引数に応じてスタブ値を変える

(上記の「複数インスタンスのメソッドが呼ばれる度に異なる値を返す」とあまり変わりませんが。。。)
元のメソッド引数もそのまま渡されるので、それを使用します。

values = { a: 10, b: 20 }
allow(foo).to receive(:baz) { |arg| values[arg] }
expect(foo.baz(:a)).to eq 10
expect(foo.baz(:b)).to eq 20

複数メソッドをまとめてスタブする

allow(foo).to receive_messages(hoge: 1, fuga: 2)
expect(foo.hoge).to eq 1
expect(foo.fuga).to eq 2

メソッドチェインをまとめてスタブする

allow(foo).to receive_message_chain(:bar, :to_s).and_return(10)
expect(foo.bar.to_s).to eq 10

元のメソッドを呼ぶ(スタブしない)

任意のメソッドが呼ばれたかのチェックはされますが、スタブされずそのまま元のメソッドが呼ばれます。(サンプルコードは簡略化しすぎて自明になってますが・・・)

allow(foo).to receive(:bar).and_call_original
expect(foo.bar).to eq 1

複数のmatcherをand/orでつなぐ

mathcherはand/orでつなぐことができます。ただし not_to (to_not)の場合は繋げられません。
失敗時には対応したmatcherのメッセージがちゃんと表示されます。

expect(1).to be_present.and be_integer.and be_odd
expect(0).to be_blank.or be_zero

# 以下のように書くのと同じです。
expect(1).to be_present
expect(1).to be_integer
expect(1).to be_odd

matcherをネストさせる

ちなみに receive().with() で使用できるArgument Matcherも使用できます。

val = [
  { foo: "foofoo", bar: { hoge: 10, fuga: 20 }, baz: 0 },
  { foo: "beebee", bar: { hoge: 30, fuga: 40 }, baz: [5, 6, 7] }
]
expect(val).to match_array([
  include(foo: be_include("oo"), bar: kind_of(Hash)),
  include(foo: be_include("ee"), baz: array_including(5))
])

こういうこともできるんだよ、という意図で本記事を書きましたが、RSpecで凝ったことをやろうとすることは対象コードのテスタビリティが低いということかもしれず、設計を見直した方がいいかもしれません。

17
14
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
17
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?