3

I'm working through Michael Hartl's Rails tutorial and can't get one Rspec test to pass when refactoring with a matcher.

terminal output

Failures:

  1) Authentication login with invalid information
     Failure/Error: it { should have_error_message('Invalid') }
     NoMethodError:
       undefined method `has_error_message?' for #<Capybara::Session>
     # ./spec/requests/authentication_pages_spec.rb:21:in `block (4 levels) in <top (required)>'

spec/support/utilities.rb

RSpec::Matchers.define :have_error_message do |m|
  match do |page|
    page.should have_selector('div.alert.alert-error', text: m)
  end
end

spec/requests/authentication_pages_spec.rb

require 'spec_helper'

describe "Authentication" do 

  subject { page }

  describe "login page" do
    before { visit login_path }

    it { should have_selector('h1',    text: 'Login') }
    it { should have_selector('title', text: 'Login') }
  end

  describe "login" do
    before { visit login_path }

    describe "with invalid information" do
      before { click_button "Login" }

      it { should have_selector('title', text: 'Login') }
      it { should have_error_message('Invalid') }
    end

    describe "after visiting after page" do
      before { click_link "Home" }
      it { should_not have_selector('div.alert.alert-error') }
    end

    describe "with valid information" do
      let(:user) { FactoryGirl.create(:user) }
      before { valid_login(user) }

      it { should have_selector('title', text: user.name) }
      it { should have_link('Profile', href: user_path(user)) }
      it { should have_link('Logout', href: logout_path) }
      it { should_not have_link('Login', href: login_path) }

      describe "followed by logout" do
        before { click_link "Logout" }

        it { should have_link('Login') }
      end
    end
  end
end

Why is it complaining about a has_error_message? method that's not defined anywhere?

Edited/added:

spec/spec_helper.rb

require 'rubygems'
require 'spork'
#uncomment the following line to use spork with the debugger
#require 'spork/ext/ruby-debug'

Spork.prefork do
  # Loading more in this block will cause your tests to run faster. However,
  # if you change any configuration or code from libraries loaded here, you'll
  # need to restart spork for it take effect.
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'
  require 'rspec/autorun'

  # 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
    #
    # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
    #
    # config.mock_with :mocha
    # config.mock_with :flexmock
    # config.mock_with :rr
    config.mock_with :rspec

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

    # 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

    # If true, the base class of anonymous controllers will be inferred
    # automatically. This will be the default behavior in future versions of
    # rspec-rails.
    config.infer_base_class_for_anonymous_controllers = false
  end
end

Spork.each_run do
  # This code will be run each time you run your specs.

end
wikichen
  • 2,253
  • 3
  • 18
  • 28

2 Answers2

3

Are you require'ing your spec/support/utilities.rb file? You may need something like this in your spec/spec_helper.rb file to actually load the file where you define the custom matcher:

Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

Update: If you're using Spork, your utilities.rb file may be getting cached by the Spork server, meaning you would need to restart your master Spork process before the new matcher could be used by RSpec (see details on the Spork wiki).

Some people use this trick to force Spork to always reload support files:

Spork.each_run do
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
end
Stuart M
  • 11,458
  • 6
  • 45
  • 59
  • It seems like I do have that line in my spec file, so that's not causing the problem, especially since the `valid_login` method I have in the same `utilities.rb` file is working just fine. – wikichen Apr 07 '13 at 00:33
  • 1
    Can you test whether your custom matcher is actually being used? Try putting a `raise RuntimeError` right before `page.should have_selector...` and if it does not raise an error, it might indicate your matcher isn't getting registered with RSpec properly somehow – Stuart M Apr 07 '13 at 00:37
  • Also, have you restarted your Spork server after writing this new matcher? See my update to the answer above for another possibility related to Spork – Stuart M Apr 07 '13 at 00:40
  • I don't have Spork running and it still raises the error; I tried adding the `RuntimeError` and it looks like the matcher does get run: `Failures: 1) Authentication login with invalid information Failure/Error: it { should have_error_message('Invalid') } RuntimeError: RuntimeError # ./spec/support/utilities.rb:11:in `block (2 levels) in ' # ./spec/requests/authentication_pages_spec.rb:21:in `block (4 levels) in '` – wikichen Apr 07 '13 at 00:42
  • 1
    What version of Capybara are you using? Might this be the same thing as http://stackoverflow.com/questions/13573525/rspec-capybara-2-0-tripping-up-my-have-selector-tests ? – Stuart M Apr 07 '13 at 00:48
  • It seems like its trying to use the [built-in matcher](https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers) `actual.should have_xxx(:arg) # passes if actual.has_xxx?(:arg)`. What happens if you rename the matcher to something else that doesn't begin with `have_` ? – Stuart M Apr 07 '13 at 00:54
  • 1
    Okay, for some freak reason I decided to recopy the methods from the tutorial site... and this time it worked. That `has_error_message?` error is going to haunt me for a long time. In any case, thanks so much for the help- I'm going to accept this as the answer. – wikichen Apr 07 '13 at 00:56
  • I had the same problem, and recopying the methods worked for me to... Inspected pasted content with a binary editor to look for non-printing character weirdness but couldn't find any... – MZB Jun 30 '14 at 21:50
2

If you're using a later version of Capybara or RSpec (ie not the ones explicitly specified in the tutorial), if I recall correctly the way that those custom matchers needed to be written changed (though I could be wrong about that) Nevertheless, try changing your method from

RSpec::Matchers.define :have_error_message do |m|
  match do |page|
    page.should have_selector('div.alert.alert-error', text: m)
  end
end

to:

RSpec::Matchers.define :have_error_message do |m|
  match do |page|
    page.has_selector?('div.alert.alert-error', text: m)
  end
end

This will then hopefully enable you to write tests like:

it { should have_error_message("Invalid") }
it { should_not have_error_message("Invalid") }

I had a similar issue that I outlined in this StackOverflow Q&A.

Community
  • 1
  • 1
Paul Fioravanti
  • 16,423
  • 7
  • 71
  • 122
  • Ah, this wasn't my problem as I was using v1.1.2 of Capybara, but I'm sure I'll upgrade the gem at some point in the future, so this is super helpful to know. Thanks! – wikichen Apr 07 '13 at 00:57