RSpec: Leverage the power of Capybara Finders and Matchers for view specs

Updated . Posted . Visible to the public.

View specs Show archive.org snapshot are a powerful tool to test several rendering paths by their cases instead of using a more costing feature spec. This is especially useful because they become quite convenient when used with Capybara::Node::Finders Show archive.org snapshot and Capybara::RSpecMatchers Show archive.org snapshot . This allows to wirte view unit specs as you can isolate specific parts of the rendered view and then use readable and powerful matchers to assert what you except to see within that part.

How to use

You can simply create a Capybara::Node::Simple object from the rendered string in your view spec by following the approach shown in the linked article Validating Views with Capybara Queries Show archive.org snapshot .

As taken from the linked article you can do:

it 'does have a nav link to something' do
  ... # assign and render a view or template

  doc = Capybara.string(rendered)
  expect(doc).to have_no_css('nav a', text: 'Something')
end

Note that this will allow you to use Capybara Finders methods (e.g. #find) on the doc element and then call matchers on that element:

link_element = doc.find('a')
expect(link_element).to have_selector('.nav-link')

Info

Even though require 'capybara/rspec' includes Capybara::RSpecMatchers by default, such that rendered is converted once one of Capybara's matchers is called on it, it's default type is still a String. (As noted in the Capybara doc Show archive.org snapshot )

So if you also want to user Capybara's finders, you still will have to convert these by yourself.

Enhanced style for view specs

  • This approach allows your view specs to be styled with enhanced readability by isolating the relevant part of the page within a subject block.
  • You can also use describe to specify which parts of your html you are currently interested in naming it with it's actual selector.
  • The context can be used to hold information on the current state of the view render

Example

Let's say we have a view which decides to render a link to the associated user or a just text of it's name depending on his soft deleted state.

describe 'movies/show' do
  describe '.content--attribute.-for-user' do
    subject(:rendered_attribute) do
      capybara_node = Capybara.string(rendered)
      capybara_node.find('p.content--attribute.-for-user')
    end

    context 'when the creator of the movie is not soft deleted' do
      it 'renders the creators name as a link to his show view' do
        # create movie and associated user 'Carl Gustav'
        
        assign(:movie, movie)
        render
        
        expect(rendered_attribute).to have_selector("a[href=\"#{user_path(user)}\"]", text: 'Carl Gustav')
      end
    end
    
    context 'when the creator of the movie is soft deleted' do
      # spec that the user is displayed as text only
    end 
  end
end

Limitations

A general downside with these specs is, that your are not going through the controller by using the routes as you would with request specs. This means that you are creating a faked state on your view template, which might not be possible to create in a real scenario.

So prefer to use these kind of specs for rather simple cases, since complex states within your app might change while you will might not notice this on your view specs!

View helpers are not included by defaulft

Helpers used within your view are not included by default. Thus, you may to stub them or include all helpers of a controller on purpose. There is also an option to automatically include all helpers.

For more details read our card RSpec: Use helpers in view specs

Concerning authorization

Especially using view-specs for authorization-dependent if-else-cases in your view might be too isolated, since view-specs will mock a lot of rails behavior and render the view independent from the controller-logic.

Therefore it will be more applicable to test views within request specs. But, even then, you still can use Caypbara's matchers within request-specs by including them separetly for request specs:

RSpec.configure do |config|
  config.include Capybara::RSpecMatchers, type: :request
end
Felix Eschey
Last edit
Felix Eschey
License
Source code in this card is licensed under the MIT License.
Posted by Felix Eschey to makandra dev (2023-10-24 06:17)