8
votes

I'm just getting started with feature specs using RSpec (and Capybara). I'm testing my ActiveAdmin dashboard and I want to check that all panels have an orders table as shown in this snippet:

feature 'admin dashboard', type: :feature do
  def panels
    page.all('.column .panel')
  end

  describe 'all panels' do
    it 'have an orders table' do
      expect(panels).to all(have_css('table.orders tbody'))
    end
  end
end

I've used the all matcher a lot in my unit tests but it doesn't appear to work when wrapping Capybara's have_css matcher because I'm getting the following error:

Failure/Error: expect(panels).to all(have_css('table.orders tbody'))
TypeError:
  no implicit conversion of Capybara::RackTest::CSSHandlers into String

Am I correct in my assumption that RSpec's built-in all matcher should work with other matchers as well?

Note: I'm using describe and it instead of feature and scenario in this instance because I'm testing output rather than user interaction scenarios (see my other question).

2
Unfortunately there is a conflict between RSpec's all and Capybara's all see Capybara Issue 1396. The all that you are calling is actually Capybara's all.Justin Ko
D'oh, you're right @JustinKo. Do you have a workaround to suggest?tristanm

2 Answers

12
votes

Unfortunately there is a conflict between RSpec's all and Capybara's all see Capybara Issue 1396. The all that you are calling is actually Capybara's all.

Solution 1 - Call BuiltIn::All Directly

The quickest solution would be to call RSpec's all method directly (or at least that code that it executes.

The expectation will work if you use RSpec::Matchers::BuiltIn::All.new instead of all:

expect(panels).to RSpec::Matchers::BuiltIn::All.new(have_css('table.orders tbody'))

Solution 2 - Redefine all

Calling the BuiltIn:All directly does not read nicely so might get annoying if used often. An alternative would be to re-define the all method to be RSpec's all method. To do this, add the module and configuration:

module FixAll
  def all(expected)
    RSpec::Matchers::BuiltIn::All.new(expected)
  end
end

RSpec.configure do |c|
  c.include FixAll
end

With the change, the all in the following line will behave like RSpec's all method.

expect(panels).to all(have_css('table.orders tbody'))

Note that if you want to use Capybara's all method, you would now always need to call it using the session (ie page):

# This will work because "page.all" is used
expect(page.all('table').length).to eq(2)

# This will throw an exception since "all" is used
expect(all('table').length).to eq(2)
0
votes

I used a very similar approach to the accepted answer, but in a Cucumber environment I was getting errors about RSpec.configure not existing. Also, I wanted to call the matcher something besides all so that I could use them both without conflicts. This is what I ended up with

# features/support/rspec_each.rb

module RSpecEach
  def each(expected)
    RSpec::Matchers::BuiltIn::All.new(expected)
  end
end

World(RSpecEach) # extends the Cucumber World environment

Now I can do things like:

expect(page.all('#employees_by_dept td.counts')).to each(have_text('1'))