Posted at

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")