Rspec-core: Feature request: Allow skipping before/after hook in examples

Created on 26 Nov 2013  路  22Comments  路  Source: rspec/rspec-core

Sometimes I found it would be nice if I could skip before hook in certain examples. I have setup something in before hook, but within this context I have some examples need different setup.

Ideally the supposed syntax would be like:

describe "Skip hook demo" do
    before(:unskipped) do
        puts "before hook"
    end

    it "will use before hook" do
    end

    it "will not use before hook", skip: true do
    end

end

Expected output

  # before hook used only one time
 "before hook"  

Most helpful comment

Besides, the functionality you're looking for exists, just in an around about fashion:

before(:each, specific_specs: true) do
  puts "before hook runs"
end

it 'wont run the before hook' do
  true
end

it 'will run the before hook', specific_specs: true do
  true
end

All 22 comments

I think you can use metadata in this case.

# Though this is digressive, RSpec 3.0.0 drops support for `skip: true`.
RSpec.configure do |config|
  config.treat_symbols_as_metadata_keys_with_true_values = true
end

describe "Skip hook demo" do
  # If prior to RSpec 2.99.0.beta1
  before do
    puts "before hook" unless example.metadata[:skip]
  end

  # If RSpec 2.99.0.beta1 or later
  before do |example|
    puts "before hook" unless example.metadata[:skip]
  end

  it "will use before hook" do
  end

  it "will not use before hook", :skip do
  end
end

@yujinakayama, thanks a lot for your response. I'm currently using similar solution, from this post https://coderwall.com/p/_yrafw. It's nice to learn metadata value can be further ignored for true now.

I personally find this pattern quite common especially when testing unhappy path. I wonder if this metadata if block can be further shrunk if more people need that.

@billychan for my $0.02 I think it's better to split your examples up by contexts if they need different before/after hooks run around them.

@myronmarston @alindeman @JonRowe @soulcutter Any objections to closing this? I don't think it's a feature we need to add.

@xaviershay wouldn't want to leave you out. Thoughts?

@samphippen, thanks for your opinion. Let me show an example which may make some sense.

describe "Issues API"
  context "index" do
    subject { JSON.parse(response.body) }
    let(:base_path) { 'api/v1/issues' }

    before do
      get base_path
    end

    it "returns data and meta" do
      subject.should return_data_and_meta
    end

    it "has default sort by time" do
      subject['data'].should be_sorted_by_time
    end

    it "accepts custom param by comments count" do
      pending "I can't use setup in this context as I want to add param to path"
    end

  context "index with custom param" do
    subject { JSON.parse(response.body) }
    let(:base_path) { 'api/v1/issues' }

    before do
      get "#{base_path}?sort_by=comments"
    end

    it "accepts custom param by comments count" do
      subject.should sort_by_comments_count
    end

  end
end

Another option is to separate all tests to two further two contexts by using or not using default path.

Things would be much simpler if I can skip before hook in last test which only need change of url but can utilize all other setup within this context, or without extra hierarchy for only one example.

@billychan you can nest contexts. This way you don't need to change the the subject or let(:base_path), you could pull that ?sort_by=comments out into another let(:query_string) and have it empty in the parent context. That way you don't need to change your before at all for the tests which use different querystrings.

@samphippen , I got your idea. Thanks. let(:query_string) is smart. I'm fine with that, actually:) as well as metadata solution mentioned by @yujinakayama. But I personally think same hierarchy would be more beautiful. So this issue is just a suggestion. Never mind if no necessity for doing that.

before is intended when you have some setup that you want to run before each example. If you don't want it to run before each of them then it's probably not the right construct to use. You're better of using a helper method or a let declaration. Overuse of constructs like before, particularly when it applies only to some examples, leads to confusing specs that are harder to reason about. The metadata thing discussed above works. I don't think I want RSpec to have a simpler way of accomplishing this because I don't want to encourage the use of before in this way.

Besides, the functionality you're looking for exists, just in an around about fashion:

before(:each, specific_specs: true) do
  puts "before hook runs"
end

it 'wont run the before hook' do
  true
end

it 'will run the before hook', specific_specs: true do
  true
end

@JonRowe, nice to know that! :+1:

@JonRowe Do we have a reverse of what you posted. specifc_spec: true example needs to skip the before block?

Sort of:

describe("something", specific_specs: true) do
  before(:each, specific_specs: true) do
    puts "before hook runs"
  end

  it 'wont run the before hook', specific_specs: false do
    true
  end

  it 'will run the before hook' do
    true
  end
end

No, Something like,

before(:each, specific_specs: true) do
  puts "before hook runs"
end

it 'will run the before hook' do
  true
end

it 'wont run the before hook', specific_specs: true do
  true
end

Similiar to :skip

No you can't skip before hooks in that way, you have to have a metadata that doesn't match, you can do this as I describe by having a default metadata thats overridden.

describe("something", specific_specs: false) do
  before(:each, specific_specs: false) do
    puts "before hook runs"
  end

  it 'will run the before hook' do
    true
  end

  it 'wont run the before hook', specific_specs: true do
    true
  end
end

@JonRowe If we configure like above, Before hook is not running for both the example.

@arunagirinathan-rajan I've just checked it, it works fine.

Randomized with seed 33735


  something
    wont run the before hook
before hook runs
    will run the before hook

Finished in 0.31496 seconds (files took 5.93 seconds to load)
2 examples, 0 failures

Randomized with seed 33735

@JonRowe Why you are specifying specific_specs: false in describe?

Having metadata on a group applies the metadata to all the examples.

The before block then will match the metadata from the describe, and the one(s) that don't match will "skip" the block.

Is this possible?

before(:each, specific_specs: nil) do
  puts "before hook runs"
end

it 'will run the before hook' do
  true
end

it 'wont run the before hook', :skip_before_hook do
  true
end

Try it and see? Sorry I've run out of time to help with this further. Stack overflow is for questions like this, not github issues.

I've tried its not working, Just expected possible or not? Anyways Thanks

I didn't expect it to work because metadata likes actual values, you should use the method I described, you can always setup an alias (see: https://relishapp.com/rspec/rspec-core/v/3-8/docs/configuration/create-example-aliases) to create the example meta data by default.

Was this page helpful?
0 / 5 - 0 ratings