ConorSheehan1
ConorSheehan1

Reputation: 1725

rspec it block inside loop

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

Answers (1)

Simple Lime
Simple Lime

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

Related Questions