2

I could be barking up the wrong tree here, but I'm trying to build a shoulda matcher for rails view specs.

Right now I have a view spec that looks something like this:

require 'rails_helper'

RSpec.describe 'layouts/_navigation', :type => :view do

  context 'everyone' do
    before :each do
      allow(view).to receive(:user_signed_in?).and_return(false)
      render
    end  

    it 'shows links to start reading' do
      expect(rendered).to have_link(I18n.t(:start_reading), :href => stories_path)
    end

    it 'shows links to home page' do
      expect(rendered).to have_link(I18n.t(:unknown_tales), root_path)
    end
  end
end

What I'd like to be able to replace this with is something like this:

require 'rails_helper'

RSpec.describe 'layouts/_navigation', :type => :view do

  context 'everyone' do
    before :each do
      allow(view).to receive(:user_signed_in?).and_return(false)
      render
    end  


    it {should have_valid_link(I18n.t(:start_reading), stories_path)}
    it {should have_valid_link(I18n.t(:unknown_tales), root_path)}
  end
end

I've taken a stab at this, with the following, but it doesn't work:

RSpec::Matchers.define :have_valid_link do |title, path|
  description { "have valid link to #{path} on #{title}" }

  match do |rendered|
    expect(rendered).to have_link(title, :href => path)
  end

end

I'm not sure if this doesn't work because I'm doing it wrong, or if shoulda matchers was never designed to work with views.

Thanks

Daniel Hollands
  • 6,281
  • 4
  • 24
  • 42

1 Answers1

2

I'm not entirely confident of this answer in the sense that there may be alternate/simpler ways of approaching this, but here's my understanding:

  • I think it's just a regular RSpec "matcher" that you are seeking here. shoulda is a gem containing a particular set of matchers.

  • You need to pass rendered to your matcher, either explicitly or implicitly, through subject.

  • Your matcher needs to turn the rendered string into a Capybara node in order to use the Capybara matchers, e.g. through Capybara.string

  • The match method is expected to return a boolean, not invoke expect

Taken together, this gives you:

require 'rails_helper'

RSpec::Matchers.define :have_valid_link do |title, path|
  description { "have valid link to #{path} on #{title}" }

  match do |rendered|
    Capybara.string(rendered).has_link?(title, :href => path)
  end
end

RSpec.describe 'layouts/_navigation', :type => :view do
  context 'everyone' do
    subject { rendered }

    before :each do
      allow(view).to receive(:user_signed_in?).and_return(false)
      render
    end  

    it {should have_valid_link(I18n.t(:start_reading), stories_path)}
    it {should have_valid_link(I18n.t(:unknown_tales), root_path)}
  end
end
Peter Alfvin
  • 28,599
  • 8
  • 68
  • 106
  • Thank you for your answer, but it doesn't work. The error I'm getting says `expected "everyone" to have valid link to /read on Start Reading` which I think is because the value of `rendered` is the string `everyone`. Maybe there is another way of accessing the rendered view? – Daniel Hollands Jan 02 '15 at 15:45
  • Did you try the code exactly as I specified in my answer? You'd get the behavior you're seeing (i.e. implicit `subject` of "everyone") if you didn't specify `subject { rendered }`. – Peter Alfvin Jan 03 '15 at 14:23
  • Sorry, my bad, I missed the `subject { rendered }` line, Added and it works. You're a star, thank you. – Daniel Hollands Jan 05 '15 at 09:27