Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep rspec tests DRY with lots of "have_link"

I'm new to Ruby on Rails and I'm doing http://ruby.railstutorial.org right now.

From what I understand the language is supposed to follow this DRY standard wery strictly but it's so WET when it comes to test driven development in this tutorial.

For example

it { should have_link('Users', href: users_path) }
it { should have_link('Profile', href: user_path(user)) }
it { should have_link('Settings', href: edit_user_path(user)) }
it { should have_link('Sign out', href: signout_path) }

Here we have lots of rows that almost looks the same.

I'we tried this

it "should have following links from this array" do
    [
        ['Users', href: users_path],
        ['Profile', href: user_path(user)],
        ['Settings', href: edit_user_path(user)],
        ['Sign out', href: signout_path]
    ].each { |a| page.should have_link(a[0], a[1]) }
end

This code works but it's looks ugly and it's more rows.

So I want to know if it's an better way to add an array to have_link method.


I now have a great idea but I don't know how to make it work.

This is my helper (it does not look like it did when i created this question. It is edited after an answer from Michaël Witrant)

RSpec::Matchers.define :have_these_links do |*links|
    match do |actual|
        links.each do |link|
            have_link(link.first, link.extract_options!).matches?(actual)
        end
    end
end

and this should now be my test

it { should have_these_links(['Users', href: users_path],
                        ['Profile', href: user_path(user)],
                        ['Settings', href: edit_user_path(user)],
                        ['Sign out', href: signout_path]) }

So this works but it's not user friendly. When I run the test and I have an link does not exist on the page it tells me that I do not have these links. But I will be able to make the helper tell me which link I'm missing. This is my error code

   expected #<Capybara::Session> to have these links ["Users", {:href=>"/users"}], ["Test Link", {:href=>"/Does_not_exist"}], and ["Profile", {:href=>"/users/991"}]
 # ./spec/requests/authentication_pages_spec.rb:42:in `block (4 levels) in <top (required)>'
like image 628
aross Avatar asked Aug 11 '12 19:08

aross


2 Answers

You can write a custom matcher, but I think thats not the idea of tests and DRY.

In the code, the DRY mantra encourages to keep every piece of knowledge of your software in a unique and unambiguous place. That is not the goal of the specs. The goal of the specs is to poof the correctness of a software in a explicit and easy to read way.

Repeat

it { should have_link('Users', href: users_path) }

if far more readable and easy to read than declaring and array of [text, url] and iterate over them, even inside some kind of custom matcher.

In test you should prefer readability over conciseness.

like image 54
miguel.camba Avatar answered Oct 14 '22 06:10

miguel.camba


To define custom matchers you can read this feature and some inspiration.

And write something like that:

RSpec::Matchers.define :have_links do |expected|
  match do |actual|
    expected.all? do |name, options|
      have_link(name, options).matches?(actual)
    end
  end
end

But IMO, your first try is the best way to write it: clean and easy to read.

like image 35
Michaël Witrant Avatar answered Oct 14 '22 05:10

Michaël Witrant