Help us understand the problem. What is going on with this article?

RSpecのshouldはもう古い!新しい記法expectを使おう!

More than 5 years have passed since last update.

RspecのVersion2.11から

old_spec.rb
foo.should eq(bar)
foo.should_not eq(bar)

new_spec.rb
expect(foo).to eq(bar)
expect(foo).not_to eq(bar)

というように書くようになりました。

別にshouldを使った記法がなくなったわけではありませんが、
https://github.com/rspec/rspec-expectations
のREADME.mdには、もう新しいSyntaxの説明しか載っていないし、今後はexpectの方を使っていくほうがいいでしょう。

http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
には、新しいSyntaxを導入した背景が説明されています。
簡潔に書くと、shouldだとBasicObjectを継承したクラスのテストを書くときに不具合が起こるみたいですね。

移行方法

基本

基本的には、上に書いたように、

  • foo.shouldexpect(foo).to
  • foo.should_notexpect(foo).not_to

置き換えるだけです。

比較演算子をとる

ただし、 shouldの後に直接オペレータをとる記法は使えなくなりました。
具体的には、

old_operator_expectations.rb
foo.should == bar
"a string".should_not =~ /a regex/
[1, 2, 3].should =~ [2, 1, 3]

foo.should < bar  # <=, >, >= も同様

new_operator_expectations.rb
expect(foo).to eq bar
expect("a string").not_to match /a regex/
expect([1, 2, 3]).to match_array [2, 1, 3]

expect(foo).to be < bar  # <=, >, >= も同様

のように変えます。
つまり、

  • ==の代わりにeq
  • 正規表現関係の=~の代わりにmatch
  • 配列の=~の代わりにmatch_array

を使えばいいんですね。

shouldの後に比較演算子<をとる記法の代わりは、be <というようにbeを入れれば解決します。
should(to) less thanよりshould(to) be less thanの方が英語的に正しいので、これはこれでOKですね。

ブロックをとる

また、このexpect記法だと、ブロックをとることができるので、

old_block_expectations.rb
lambda { do_something }.should raise_error(SomeError)

new_block_expectations.rb
expect { something }.to raise_error(SomeError)

と書けるので、統一感があっていいですね。

移行が終わったら

config/initializers/rspec.rb
RSpec.configure do |config|
  config.expect_with :rspec do |c|
    c.syntax = :expect    # disables `should`
  end
end

とすると、今後shouldを使った書き方ができなくなります。

ちなみにデフォルトでは

    c.syntax = [:should, :expect]  # enables both `should` and `expect`

と設定したのと同じになっています。

他のシチュエーションの書き方一覧

基本的に置き換えは上記の通り行うと済みますが、参照用に
https://github.com/rspec/rspec-expectations
に載っている他の書き方をそのまま転載しておきます。

Equivalence

expect(actual).to eq(expected)  # passes if actual == expected
expect(actual).to eql(expected) # passes if actual.eql?(expected)

Note: The new expect syntax no longer supports == matcher.

Identity

expect(actual).to be(expected)    # passes if actual.equal?(expected)
expect(actual).to equal(expected) # passes if actual.equal?(expected)

Comparisons

expect(actual).to be >  expected
expect(actual).to be >= expected
expect(actual).to be <= expected
expect(actual).to be <  expected
expect(actual).to be_within(delta).of(expected)

Regular expressions

expect(actual).to match(/expression/)

Note: The new expect syntax no longer supports =~ matcher.

Types/classes

expect(actual).to be_an_instance_of(expected)
expect(actual).to be_a_kind_of(expected)

Truthiness

expect(actual).to be_true  # passes if actual is truthy (not nil or false)
expect(actual).to be_false # passes if actual is falsy (nil or false)
expect(actual).to be_nil   # passes if actual is nil

Expecting errors

expect { ... }.to raise_error
expect { ... }.to raise_error(ErrorClass)
expect { ... }.to raise_error("message")
expect { ... }.to raise_error(ErrorClass, "message")

Expecting throws

expect { ... }.to throw_symbol
expect { ... }.to throw_symbol(:symbol)
expect { ... }.to throw_symbol(:symbol, 'value')

Yielding

expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args

expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded

expect { |b| 5.tap(&b) }.to yield_with_args(5)
expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum)
expect { |b| "a string".tap(&b) }.to yield_with_args(/str/)

expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])

Predicate matchers

expect(actual).to be_xxx         # passes if actual.xxx?
expect(actual).to have_xxx(:arg) # passes if actual.has_xxx?(:arg)

Ranges (Ruby >= 1.9 only)

expect(1..10).to cover(3)

Collection membership

expect(actual).to include(expected)
expect(actual).to start_with(expected)
expect(actual).to end_with(expected)

Examples

expect([1,2,3]).to include(1)
expect([1,2,3]).to include(1, 2)
expect([1,2,3]).to start_with(1)
expect([1,2,3]).to start_with(1,2)
expect([1,2,3]).to end_with(3)
expect([1,2,3]).to end_with(2,3)
expect({:a => 'b'}).to include(:a => 'b')
expect("this string").to include("is str")
expect("this string").to start_with("this")
expect("this string").to end_with("ring")
awakia
検索とか推薦とかやってきたエンジニア。早稲田の山名研出身。大学院の頃、論文を書こうとしない僕を見かねた教授に、北京のMSRAに追放されるが3ヶ月後無事帰還。 大学を卒業後、エンジニアのブラックホールとの別名を持つGoogleに吸収されそうになるが1年2ヶ月後無事生還。 現在は、Wantedly(https://www.wantedly.com/ )の4番目のエージェントとして救出活動に専念。
http://awakia-n.hatenablog.com/
wantedly
「シゴトでココロオドル」ためのビジネスSNS「Wantedly」の開発・運営をしています。
https://wantedlyinc.com/ja/presentations
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした