Blog

Upgrading to RSpec 2 with Ruby on Rails 3

Jonathon Horsman Oct 15 11 comments

Following on from my previous post about upgrading our Rails 2.3.5 application to Rails 3 the most time consuming part was getting our 600 tests passing again.

We use Rspec (upgraded to version 2), Ruby Double (rr) for mocking and Factory Girl.

Here’s the process I followed to get our recently upgraded Rails 3 app working with Rspec 2.

There’s a special place in my heart for the seemingly deliberately terse docs which come with Rspec.

The first 2 lines of the home page says it all:

Overview
Behaviour Driven Development for Ruby.

and that’s it.

Perhaps they’re worried about running out of space on the internet?

Anyway I digress, getting my tests all working again was a bit of a mission.

Update the Gemfile

First the updated Gem versions. At the bottom of my Gemfile they look like this:

group :test do
  gem "rspec"
  gem "rspec-rails"
  gem "autotest"
  #gem "factory_girl", "2.0.0.beta1"
  gem "factory_girl_rails"
  gem "rr"
end

New Spec Helper file

The next step is to update to the new spec_helper.rb file:

ENV["RAILS_ENV"] ||= 'test'

require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'thinking_sphinx/test'

# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

RSpec.configure do |config|
  # == Mock Framework
  config.mock_with :rr

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  
  ThinkingSphinx::Test.init

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true
end

Broken backwards compatibility

For apparently no good reason there’s a bunch of things which just no longer work.

Sometimes it’s just because they’ve decided to rename the methods.

For instance in all my controller tests I had to do a global search for integrate_views and replace with render_views

Rails params bug

Previously all params in a test get/post/delete/put request would be converted to strings, just as they would appear to your controller in the real world.

However there’s a bug in Rails 3 which means this no longer happens. So where this used to pass:

describe "find contact" do
  before { mock(Contact).find("1234") { Contact.new } } # expect a string for the ID
  describe "successfully" do
    before { get :show { :id => 1234 } } # integer param passed. This used to be converted to a string for me
    it "should render the show view" do
      response.should render_template("show")
    end
  end
end

it now fails with:

unexpected method invocation:
find(1234)
expected invocations:
- find("1234")

I had about 300 of these tests failing and I thought I was going to have to modify every one of them.

Interestingly after digging through the Rails code I found the bug. Evidently this “params massaging” was not deliberately removed because the code is still there. It just overwrites the massaged parameters with the original parameters.

So here’s my fix. Create the file spec/support/action_controller.rb and copy in the content:

module ActionController
  class TestCase < ActiveSupport::TestCase
    module Behavior
      def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
        # Sanity check for required instance variables so we can give an
        # understandable error message.
        %w(@routes @controller @request @response).each do |iv_name|
          if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
            raise "#{iv_name} is nil: make sure you set it in your test's setup method."
          end
        end
        
        @request.recycle!
        @response.recycle!
        @controller.response_body = nil
        @controller.formats = nil
        @controller.params = nil
        
        @html_document = nil
        @request.env['REQUEST_METHOD'] = http_method
        
        parameters ||= {}
        @request.assign_parameters(@routes, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
        
        @request.session = ActionController::TestSession.new(session) unless session.nil?
        @request.session["flash"] = @request.flash.update(flash || {})
        @request.session["flash"].sweep
        
        @controller.request = @request
        #@controller.params.merge!(parameters) # this is the offending line, which I removed
        build_request_uri(action, parameters)
        Base.class_eval { include Testing }
        @controller.process_with_new_base_test(@request, @response)
        @request.session.delete('flash') if @request.session['flash'].blank?
        @response
      end
    end
  end
end

now all those tests should start passing again.

RSpec matchers

A bunch of matchers seem to have been taken out now, but they’re easy enough to roll your own as demonstrated in this video

I wrote the include_text matcher so my old controller tests which look like this will now pass:

response.should include_text('Error: Your update failed')

Yes, this is a view test which doesn’t belong in the controller, but I’m not about to go through all my tests and remove a useful test.

So I created a new file called spec/support/include_text.rb and added this:

module RSpec::Rails
  module Matchers
    RSpec::Matchers.define :include_text do |text|
      match do |response_or_text|
        @content = response_or_text.respond_to?(:body) ? response_or_text.body : response_or_text
        @content.include?(text)
      end

      failure_message_for_should do |text|
        "expected '#{@content}' to contain '#{text}'"
      end

      failure_message_for_should_not do |text|
        "expected #{@content} to not contain '#{text}'"
      end
    end
  end
end

Factory Girl gem change

If you’re using Factory Girl, replace factory_girl in your Gemfile with factory_girl_rails

I don’t know the rationale behind the change but cest la vie.
edit: after looking at the code it appears Factory Girl Rails is just a small wrapper around Factory Girl, so you will still need the Factory Girl gem, probably at version 2.

Assert difference has disappeared

Another useful test method has been inexplicably removed. A complete list of everything which has been removed / renamed would be very useful. I’m looking at you, David Chelimsky

Anyways, to get my assert_difference and assert_no_difference assertions working again, I created the file spec/support/assert_difference.rb:

def assert_difference(executable, how_many = 1, &block)
  before = eval(executable)
  yield
  after = eval(executable)
  after.should == before + how_many
end

def assert_no_difference(executable, &block)
  before = eval(executable)
  yield
  after = eval(executable)
  after.should == before
end

Hey I’m getting closer, down to 72 failures.

Loading files in the lib directory

The ruby files in lib/ are no longer automatically loaded by Rails. So in application.rb I had to add this:

module Matchbook
  class Application < Rails::Application
    require Rails.root.join("lib", "matchbook", "delayed_jobs", "delete_contacts_job.rb")
    require Rails.root.join("lib", "matchbook", "delayed_jobs", "email_creator_job.rb")
    require Rails.root.join("lib", "matchbook", "auditing", "contact_audit_sweeper.rb")

    ...
  end
end

More Rspec pain

I also get a few of these errors:

     unexpected method invocation:
       valid?(nil)
     expected invocations:
     - valid?()

Come on, why aren’t they treated as equivalent now?
Well since there’s only a few of them I went and manually added the nil parameter.

Action Mailer changes

According to the docs ActionMailer methods always return objects, not strings:

Every object in a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call encoded or decoded to get the string you want.

So where these tests used to pass

    mail = SiteMailer.email(message, contact, {"message" => message_link, "image" => image_link, "promo" => promo_link})
    
    mail.body.should include_text(message_link.to_s)
    mail.body.should include_text(image_link.to_s)
    mail.body.should include_text(promo_link.to_s)

they now fail. That’s easily fixed with:

    mail = SiteMailer.email(message, contact, {"message" => message_link, "image" => image_link, "promo" => promo_link})

    body = mail.body.encoded.gsub(/=\r\n/, "")
    body.should include_text(message_link.to_s)
    body.should include_text(image_link.to_s)
    body.should include_text(promo_link.to_s)

Note I updated the SiteMailer code to work with the new Rails mailer API. Info in the guide.

Ruby Double mocking with deprecations

This no longer works:

any_instance_of(Note, :validate => nil)

and also produces deprecation warnings. This isn’t the correct rr syntax anyway, so I replaced it with

stub.any_instance_of(Note).valid?(nil) { true }

Display the full stacktrace

Run specs with the -b option to include the full backtrace, rather than just the subset which includes your code.

This is useful for identifying problems in libraries and dependencies.

All tests passing

With some further code changes to work with the new Mail API, fixes to rename a reserved word (field is a no-no column name now) and a couple of other bits and pieces, all 613 of my tests now pass!

Comments //

David Chelimsky

David Chelimsky Oct 20

I'll update the docs with things you've cited that are actually missing, but most of this information is actually readily available. Please read the following and update your post accordingly:

1. http://rspec.info - "More Information" (links to RSpec-2 documentation).

2. http://github.com/rspec/rspec-rails (README - linked from http://rspec.info).

3. http://github.com/rspec/rspec-rails/blob/master/Upgrade.markdown

Additional notes:

a. `assert_difference` is a Rails assertion. RSpec's `should change` is still available and at your disposal.

b. you're using RR for mocking. That's what is providing failure messages about invocations with or without nil.

Hope this helps you and your readers.

Cheers,
David

Kurt Snyder

Kurt Snyder Feb 01

There's a cheat sheet of RSpec1 to RSpec2 changes here: http://snyderscribbles.blogspot.com/2011/01/rspec-2-changes-from-rspec-1.html I'll post additions/corrections that anyone wants to submit.

Kimlueng

Kimlueng Sep 14

Thanks for the pointer. That'll help if I need to conrvet anything else. I'm impressed by how much Rspec has changed in just the last couple of months. I think the older release I was working from dates back only as far as the start of April.

chinacheng

chinacheng Feb 22

i think the assert_difference method could be more better, for example: executable maybe a array

Discount Codes UK

Discount Codes UK Oct 19

Hurrah! Finally I got a weblog from where I know how to truly obtain helpful facts concerning my study and knowledge.

ImminsRaisk

ImminsRaisk Nov 28

唆している。 一つには、親と子は同じ興味を共有することはでき <a href=http://www.africa-divine-safaris.com/%E3%82%B5%E3%83%B3%E3%82%B0%E3%83%A9%E3%82%B9-c-11.html>シャネル サングラス 人気</a> ットレタリングを使用したり、あなたの基本的な招待状に滑らかに <a href=http://www.alnebrasgames.com/%E3%83%9E%E3%83%95%E3%83%A9%E3%83%BC-c-35.html>マフラー</a> いキャリアの見通しを活用することができます。 入手可能な最新 <a href=http://www.bvlgariperfumejp.com/%E3%83%96%E3%83%AB%E3%82%AC%E3%83%AA%E9%A6%99%E6%B0%B4%E3%83%AC%E3%83%87%E3%82%A3%E3%83%BC%E3%82%B9-c-1.html>香水 おすすめ</a> う。

 単一の株式仲買人は、よくやった - 辛うじて <a href=http://www.djbrixx.com/category-10.html>ダウンジャケット モンクレール</a> めに静かな雰囲気を提供しています。

 占星術の歴史は文 <a href=http://www.gyikom.com/category-3.html>グッチの財布</a> デートは、あなたの途方もない空想を共有し、探求して自由であるです。 あなたは間違いなくあなたがcriminals.Bac

enetaTubinake

enetaTubinake Nov 28

く作成されたクジャンは地元の美術館で見ることができるように有 <a href=http://viewdesigncompany.com/news/moncler.php>モンクレール ワンピース</a>
 私たちはただ、時々、彼らは本当にpopular.Soい <a href=http://www.aboveboardadvisors.com/%E3%83%AD%E3%83%AC%E3%83%83%E3%82%AF%E3%82%B9-c-28.html>ロレックス ブログ</a> 1、ビジネス客のためにB2、学生ビザのようなビザの他のタイプ <a href=http://www.broerwenang.com/%E3%83%8D%E3%82%AF%E3%82%BF%E3%82%A4-c-19.html>ネクタイ 結び方</a> 特に、ほとんどの黒の女性は非常に性的である。 ステレオタイ <a href=http://www.dedicatedserver411.com/%E3%82%BB%E3%82%A4%E3%82%B3%E3%83%BC5-%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%84-c-4.html>セイコー5 日本製</a> ます。

 試験が終了するまで、彼らは定期的な生活を続け <a href=http://www.latinabarbie.net/%E3%83%A2%E3%83%B3%E3%82%AF%E3%83%AC%E3%83%BC%E3%83%AB%E3%83%A1%E3%83%B3%E3%82%BA%E3%83%99%E3%82%B9%E3%83%88-c-6.html>モンクレール ダウン 2014 メンズ</a> ンテキストに理論を入れて学生に教室の範囲外で勉強する機会を与ョブを実行する能力と意欲を示してグリーンカードを得ることがで

ImminsRaisk

ImminsRaisk Nov 28

新しいモデル車は、売りにもだが、入札はsoon.Smallビ <a href=http://bibelos.com/>スープラ</a> うに彼の下の大理石の台座に扱われます。 彼のプロポーションは <a href=http://www.als-risk.com/%E3%83%97%E3%83%A9%E3%83%80-%E3%83%9D%E3%83%BC%E3%83%81-c-1.html>プラダ ポーチ 2013</a> の財布の内側に到達し、感電する危険にしたくないので、必ず安全 <a href=http://www.burberrywholesale.com/%E3%83%90%E3%83%BC%E3%83%90%E3%83%AA%E3%83%BC%E3%83%AD%E3%83%B3%E3%83%89%E3%83%B3--c-15.html>バーバリーロンドン ネクタイ</a> 親族を探しすべき? 家系図の名前、あなたは国家重要な記録の系 <a href=http://www.fgjxmk.com/>mcm 激安</a> ないことを何かでなければなりません 予算を決めることです。
<a href=http://www.stabnsplash.com/jp/category/l4_12.html>ダウン/ダウンジャケット</a> 特権。 カリフォルニアの逮捕情報の要求はサクラメント、カリフもしれないが後はそのはるかに良い人生になります。 あなたのポ

UGGS On Sale

UGGS On Sale Dec 03

Because the admin of this web site is working, no hesitation very soon it will be famous, due to its feature contents.

vigrx oil price

vigrx oil price Mar 19

Very good post! We are linking to this particularly great article
on our site. Keep up the great writing.

コピーブランド

コピーブランド Apr 14

buranndofukuはガガミラノ コピー:http://www.buranndofuku.com/gallery-163-grid.html
 日本でも広く流通してしまっているモンクレール 偽物通販店です。
 商品の見分け方や 注意点について紹介しています。
 クロムハーツ 激安の商品特に大人気のクロムハーツ激安 ,クロムハーツ ブランド偽物 格安の種類を豊富に取り揃えます。スーパーコピーブランド:http://www.buranndofuku.com/のお求めはぜひ当店へ!
[url=http://www.buranndofuku.com/gallery-181-grid.html]コピーブランド[/url]

Post a comment

  1. optional

Recent Tweets

Blog: The monumental Myspace cock-up: http://bit.ly/emgRKV
Tweeted on Friday at 09:43

Awww railsapi, delete some logs: http://bit.ly/htBNDH
Tweeted on Wednesday at 16:15