Reputation: 5789
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)>'
Upvotes: 4
Views: 3421
Reputation: 1827
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.
Upvotes: 7
Reputation: 7714
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.
Upvotes: 3