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"
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.
Most helpful comment
Besides, the functionality you're looking for exists, just in an around about fashion: