Upgrading to RSpec 3

I recently upgraded our Rails application to use RSpec 3, which is currently in its second beta, from 2.99. I was expecting it to be nice and straightforward, but sadly it was not! This was partly because we have been using RR for mocking and stubbing, and the syntax was not compatible with RSpec 3.

In the process of upgrading I learned a bunch about the new RSpec “allow” syntax, which in my opinion is far nicer than the RR one we were using.

Here’s how to stub:

allow(thing).to receive(:method).with(args).and_return(other_thing)

Mocking can be done in the same way by substituting “allow” for “expect” – although in most cases the tests read better if you test that the stub received the method using the following matcher:

expect(thing).to have_received(:method).with(args)

That this is different to the previous RR syntax, which was expect(thing).to have_received.method(args)
You can also use argument matchers, for example:

expect(thing).to have_received(:method).with(a_kind_of(Class))

And you can verify how many times a method was called:

expect(object).to have_received(method).at_least(:once)
expect(object).to have_received(method).exactly(3).times

The .with(args) and .and_return(other_thing) methods are optional. You can also invoke a different function:

allow(thing).to receive(:method) { |args| block }

Or call the original function:

allow(thing).to receive(:method).and_call_original

Another thing we used fairly often was any_instance_of. This is now cleaner (RR used to take a block):

allow_any_instance_of(Class).to receive(:method).and_return
allow_any_instance_of(Class).to receive(:method) { |instance, args| block}

If you pass a block, the first argument when it gets called is the instance it is called on.
In RSpec 3, be_false and be_true are deprecated. Instead, use eq false or eq true. You can use be in place of eq, but when the test fails you get a longer error message, pointing out that the error may be due to incorrect object reference, which is irrelevant and kind of annoying.

Using RSpec mocks means that we can create new mock or stub objects using double(Class_or_name) rather than Object.new, which results in tidier error messages and clearer test code.

Stubbing a chain of methods may also be a handy tool – I only found one place where we used it, but it is useful if we’re chaining together methods to search models.

allow(object).to receive_message_chain(:method1, :method2)

More info:

  1. https://relishapp.com/rspec/rspec-mocks/docs
  2. https://github.com/rspec/rspec-mocks

Update: it turns out I was missing a configuration option in RSpec. It should have worked with RR by doing this:

RSpec.configure do |rspec|
  rspec.mock_with :rr
end

Thanks Myron for clearing this up :)

3 thoughts on “Upgrading to RSpec 3

  • February 25, 2014 at 8:17 am
    Permalink

    Sorry to hear the upgrade didn’t go smoothly :(.

    I’ve not been aware (until reading this blog post) of any problems with using rr with RSpec 3. Can you open a github issue describing the problems you had? We definitely want users to be able to use alternate mocking libraries like rr if that’s what they prefer.

    Reply
  • July 4, 2014 at 10:57 am
    Permalink

    Thanks for this article, not only did it helped me, it also made me aware rspec now has all features I used RR for in the first place :) (namely, `#have_received` and double graphing).

    There are two problems I’ve had that are not mentioned in your article, so I’ll leave this here :

    `#have_received` can’t be used on method chain (or I didn’t found how)

    A typical thing I was doing with RR was using double graphing was to stub mailers and check deliver was called :


    stub( FooMailer ).welcome.stub!.deliver
    FooMailer.welcome.should have_received.deliver

    But I can’t get anything working with rspec looking alike, there is no `#have_received_message_chain`, `#have_received` can’t take multiple arguments and calling `#should` on the stub like I did in RR doesn’t work either. So here is how I converted those :


    allow( @mailer = {} ).to receive :deliver
    allow( FooMailer ).to receive( :welcome ).and_return( @mailer )
    @mailer.should have_received( :deliver )

    Basically, it’s just forgetting about double graph.

    using stubs in factories from FactoryGirl

    An other thing I do often is to stub associations in my factories. Here is how I was doing it with RR :


    FactoryGirl.define do
    factory :foo do
    after :stub do |foo|
    RR.stub( foo ).bars { build_stubbed_list :bar, 3 }
    end
    end
    end

    I didn’t find how to call `#allow` from where it is not included (it’s quite hard to get what comes from where in rspec).

    But we can use `#stub` instead of `#allow` to make that easier :


    FactoryGirl.define do
    factory :foo do
    after :stub do |foo|
    foo.stub( :bars ).and_return( build_stubbed_list( :bar, 3 ) )
    end
    end
    end

    Reply

Leave a Reply to Myron Marston Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>