In RSpec-2, every example group and example has associated metadata, to which you can append arbitrary information. This allows you to slice and dice a spec suite in a variety of ways.

Adding arbitrary metadata

The describe and it methods, and their aliases, each accept a hash as the last argument before the block:

describe "something", :this => {:is => :arbitrary} do
  it "does something", :and => "so is this" do
    # ...
  end
end

filter_run

The keys in these hashes can be accessed in a number of ways via RSpec.configure. If, for example, you’re working on a specific example and don’t want to run the full suite, you can use the filter_run method on the configuration like this:

# in spec/spec_helper.rb
RSpec.configure do |c|
  c.filter_run :focus => true
end
 
# in spec/any_spec.rb
describe "something" do
  it "does something", :focus => true do
    # ....
  end
end

Now if you run rspec spec, it will only run that one example, no matter how many others there are in the suite.

This works for examples and groups, so if you want to run all the examples in one group that you’re focusing on, but nothing else, you can do this:

RSpec.configure do |c|
  c.filter_run :focus => true
end
 
describe "something", :focus => true do
  it "does something" do
    # ....
  end
  it "does something else" do
    # ....
  end
end

The rspec command would now run both of the examples in that group.

filter_run_excluding

This is the inverse of filter_run. It excludes any examples or groups that match the filter:

RSpec.configure do |c|
  c.filter_run_excluding :slow => true
end
 
describe "something", :slow => true do
  it "does something" do
    # ....
  end
  it "does something else" do
    # ....
  end
end

The rspec command would now run all the other examples in the suite, but not these two.

NOTE: filter_run_excluding was added in beta.12, which was just released this morning.

lambda

You can filter on runtime conditions by assigning a lambda to a key. If your app is expected to behave differently in different versions of Ruby, you can use a lambda with filter_run_excluding like this:

RSpec.configure do |c|
  c.filter_run_excluding :ruby => lambda {|version|
    !(RUBY_VERSION.to_s =~ /^#{version.to_s}/)
  }
end
 
describe "something" do
  it "does something", :ruby => 1.8 do
    # ....
  end
  it "does something", :ruby => 1.9 do
    # ....
  end
end

This example comes directly from rspec-core’s own spec_helper.rb.

RSpec passes 1.8 and 1.9 to the lambda, which accepts it as the version block argument. If the lambda returns true, the example is excluded from the run (because we’re using filter_run_excluding). Now the first example will only run if the ruby version is 1.8. Similarly, the latter example only runs under 1.9.

(no) command line support (yet)

We plan to add some sort of command line API to access these filters, but we’re not sure yet what this is going to look like. There is an open issue in github issues for rspec-core . Please feel free to review and add any comments there.

13 Responses to “Filtering examples in rspec-2”

  1. Joel Helbling Says:

    This is very cool. There is some overlap with using Rake to run subsets of tests, but I like this because of the granularity and because it’s easier to keep similar-but-differentiated tests close together (e.g. when two or more examples test the same thing, but under different conditions, such as your Ruby 1.8 vs 1.9 example).

  2. Rizwan Reza Says:

    Awesome, can’t wait to use this. Command line support would be awesome-r. Thanks David.

  3. Wincent Colaiuta Says:

    Any way to get at that arbitrary metadata from within config.before block?

    I’m trying to do something like the following, so that I can switch Capybara drivers depending on whether a Steak scenario needs JavaScript or not:

    Rspec.configure do |config|
      config.before :each do
        Capybara.current_driver = :culerity if options[:js]
      end

    config.after :each do Capybara.use_default_driver if options[:js] end end

    So the question is, what would I need to do to get at the metadata (obviously the “options” thing I’m using above doesn’t actually exist in RSpec 2.

    (Crosses fingers and hopes that formatting doesn’t get ruined when I click “Submit”…)

  4. Brandon Keepers Says:

    Very cool.

    Is there a way to get access to all of the metadata before the run? For example, if there are any scenarios with {:filter => true}, then only run those, otherwise, run them all.

  5. David Chelimsky Says:

    Brandon - YES!!!!!!!!

    RSpec.configure do |c|
      c.filter_run :focus => true
      c.run_all_when_everything_filtered = true
    end

    We actually use that in rspec-core’s own specs: http://github.com/rspec/rspec-core/blob/02dba1ab2b66adbf3b0b9424020eada48760332a/spec/spec_helper.rb#L54

  6. Thibaud Guillaume-Gentil Says:

    David, this focus => true feature is awesome. And this run_all_when_everything_filtered (little cryptic maybe) option is really great too. It would be even more awesome to have these configuration automatically added when launching rspec with (for example) –focus option. So users of tools like RSpactor (complete rewrite in progress!) will have access to this feature without modifying their spec_helper.rb.

    What do you think about that?

  7. David Chelimsky Says:

    Thibaud - please submit a feature request: http://github.com/rspec/rspec-core/issues. Thx

  8. Andy Ferra Says:

    @Wincent

    The little snippet under RSpec & Metadata will probably answer your question.

    https://github.com/cavalle/steak/blob/e044f2f2f21aa2181cd35bf0bda75d764f9d0284/README.rdoc

    In the example :type being the metadata being filtered upon.

  9. loan car Says:

    Have you considered adding some differing opinions to the article? I think it might enhance viewers understanding.

  10. Jonathan Says:

    I’m trying to trim the bloat on my asynchronous workers. It would be nice to filter all non-background-job related specs. But to go through each file would be a real pain.

    Is there a way for me to filter specs (in the aforementioned Rspec.configure block) by path?

    For example: all spec/controller/_spec.rb; spec/view/_spec.rb (though I would still want spec/view/mailer/*_spec.rb), etc

    Thanks

  11. Jonathan Says:

    For clarification on that last post: I am trimming the bloated workers by attempting to load less of Rails

    it is therefore, that I’d like to also trim the specs, to ensure I haven’t trimmed too much.

  12. Jonathan Says:

    just realized this was an old post (sorry). Also, I got my answer from here: https://github.com/rspec/rspec-core/issues/128

  13. http://champion-motors.yolasite.com/ Says:

    I have read several good stuff here. Definitely worth bookmarking for revisiting. I surprise how much effort you put to make such a excellent informative site.

Leave a Reply