Reputation: 1725
I want to test that every link on a page returns a certain status code. I have a working test, something like this:
it "does not throw 400/500 error for any link" do
visit '/'
within(".wrapper") do
all('a').each do |link|
http_status = Faraday.head(link[:href].to_s).status
puts "#{link.text}, #{http_status}"
expect((400..500)).not_to include(http_status)
end
end
However this is pretty terrible for output since all the links are checked in one test (hence the puts inside the test to show me which link breaks the test). I would like to be able to have a separate test for each link, without manually writing a test for every single link on the page.
I tried this:
context "does not throw 400/500 error for any link" do
visit '/'
within(".wrapper") do
all('a').each do |link|
it "does not throw 400/500 error for #{link}" do
link_status_code_is_not(link, (400..500))
end
end
end
end
and got this error
`visit` is not available on an example group (e.g. a `describe` or
`context` block). It is only available from within individual examples
(e.g. `it` blocks) or from constructs that run in the scope of an
example (e.g. `before`, `let`, etc).
so i tried moving the visit to a before hook but got the same issue with the within block (which I can't really move). I've been able to do something like this in jasmine, but I can't seem to find a way to do it in rspec.
tl:dr; Is there any way to put an it block inside a loop in rspec?
Upvotes: 5
Views: 4096
Reputation: 11035
You can create an it
block inside of a loop:
RSpec.describe "numbers" do
[*1..10].each do |number|
it "#{number} should not be multiple of 10" do
expect(number % 10).not_to be_zero
end
end
end
Your problem though is it doesn't look like you can visit
outside of an it
block (or "constructs that run in the scope of an example") and you need to grab your data from a page that you visit
and loop through and create it
blocks from that data. I'm not sure if that can be done nicely.
If what you're wanting is just better output for your links, you can use aggregate_failures
along with Customized messages to provide a better output for your test. For a contrived example, let's say I have a ton of dynamic, only available within it
blocks or from constructs that run in the scope of an example, pairs of numbers and I need to make sure none of their products are multiples of 10:
RSpec.describe "numbers" do
it "are not a multiple of 10" do
[*1..10].product([*1..10]).each do |base, multiplier|
expect((base * multiplier) % 10).not_to be_zero
end
end
end
The output here isn't very good, it just tells me expected 0.zero?
to return false, got true, not which pair of numbers failed. By using the aggregate failures and a custom message though, I can fix that:
RSpec.describe "numbers" do
it "are not a multiple of 10" do
aggregate_failures do
[*1..10].product([*1..10]).each do |base, multiplier|
expect((base * multiplier) % 10).not_to be_zero, "#{base} * #{multiplier} is a multiple of 10"
end
end
end
end
Now, I get only 1 failure reported in the stats (which may be good or bad for your use case), but I get 27 sub failures:
Failures:
1) numbers are not a multiple of 10
Got 27 failures from failure aggregation block.
1.1) Failure/Error: ...
1 * 10 is a multiple of 10
# ...backtrace...
1.2) Failure/Error: ...
2 * 5 is a multiple of 10
# ...backtrace...
...
1.27) Failure/Error: ...
10 * 10 is a multiple of 10
Upvotes: 6