RSpec 3.1 has been released!

Myron Marston

Sep 4, 2014

RSpec 3.1 has just been released! Given our commitment to semantic versioning, this should be a trivial upgrade for anyone already using RSpec 3.0, but if we did introduce any regressions, please let us know, and we’ll get a patch release out with a fix ASAP.

RSpec continues to be a community-driven project with contributors from all over the world. This release includes 647 commits from 190 pull requests from 47 different contributors.

Thank you to everyone who helped make this release happen!

Notable Changes

Core: Backtrace filtering changes

In RSpec 2.x and RSpec 3.0, the default backtrace filtering configuration excluded lines from gems from printed backraces. We got some feedback that this was unhelpful and have removed /gems/ from the default backtrace filter patterns. In RSpec 3.1, lines from gems will be included backtraces, but lines from RSpec itself will continue to be excluded. Of course, if you want still gems to be excluded, you can easily add this pattern back yourself:

# spec/spec_helper.rb
RSpec.configure do |config|
  config.backtrace_exclusion_patterns << /gems/
end

In addition, we’ve added a new API to make it easy to filter out one or more specific gems:

# spec/spec_helper.rb
RSpec.configure do |config|
  config.filter_gems_from_backtrace "rack", "rake"
end

Core: New --exclude-pattern option

RSpec 3.1 has a new --exclude-pattern option that is the inverse of --pattern. This allows you to exclude particular files, so that, for example, you can load and run all spec files except those from a particular directory:

rspec --pattern "spec/**/*_spec.rb" --exclude-pattern "spec/acceptance/**/*_spec.rb"

The rake task definition API supports this option now, too, so it is easy to define tasks that run all specs but those from one directory:

# Rakefile
require 'rspec/core/rake_task'

desc "Run all but the acceptance specs"
RSpec::Core::RakeTask.new(:all_but_acceptance) do |t|
  t.exclude_pattern = "spec/acceptance/**/*_spec.rb"
end

Thanks to John Gesimondo for suggesting and implementing this new feature!

Core: Can now be used standalone without configuration

RSpec got split up into separate gems (core, expecations and mocks) in RSpec 2.0, which allows users to mix and match parts of RSpec with other testing libraries like Minitest. Tom Stuart pointed out that it wasn’t as easy to use just rspec-core as it could be–specifically, in RSpec 3.0 and before, if you didn’t explicitly configure rspec-core to not use rspec-expectations and rspec-mocks, it assumed they were available, tried to load them, and gave you an error if they were not.

In RSpec 3.1, we’ve rectified this. rspec-expectations and rspec-mocks will still both be used by default if available, but it they are not available and you haven’t configured anything, rspec-core will work just fine.

Thanks to Sam Phippen for implementating this improvement.

Core: Warnings flag no longer defaults to true in generated files

In RSpec 3.0, we put --warnings in the generated .rspec file. We did that to encourage gem authors to make their gems warning-free (as gems that issue warnings inhibit users from using Ruby’s warning mode). However, the rails ecosystem has generally not strived to have warning-free code, and on new rails projects, this led to a ton of confusing warnings.

In RSpec 3.1, we learned from the community feedback on this and have removed --warnings from the generated .rspec file. In non-rails projects, rspec --init will include config.warnings = true in the commented-out section of recommended settings in spec_helper.rb.

Thanks to Andrew Hooker for making this change.

Expectations: New have_attributes matcher

This new matcher makes it easy to match an object based on its attributes:

Person = Struct.new(:name, :age)
person = Person.new("Coen", 3)
expect(person).to have_attributes(name: "Coen", age: 3)

It’s also aliased to an_object_having_attributes, which is particularly useful in composed matcher expressions:

people = [Person.new("Coen", 3), Person.new("Daphne", 2)]
expect(people).to match([
  an_object_having_attributes(name: "Coen",   age: 3),
  an_object_having_attributes(name: "Daphne", age: 2)
])

It can also be used as an argument matcher for a message expectation:

expect(email_gateway).to receive(:send_receipt).with(
  an_object_having_attributes(email: "foo@example.com")
)

Thanks to Adam Farhi for implementing this new matcher.

Expectations: Block matchers can now be used in compound expressions

RSpec 3.0 gained the ability to use compound matcher expressions. However, it didn’t work with block expectations because we had internal changes that needed to be made to ensure the block is only executed once as one would expect. We’ve addressed this in 3.1, which allows an expression like:

x = y = 0
expect {
  x += 1
  y += 2
}.to change { x }.to(1).and change { y }.to(2)

Expectations: New define_negated_matcher API

This new API provides a means to define a negated version of an existing matcher:

# define a negated form of `include`...
RSpec::Matchers.define_negated_matcher :exclude, :include

# ...which allows you to write:
expect(odd_numbers).to exclude(14)

# ...rather than:
expect(odd_numbers).not_to include(14)

On its own, this doesn’t buy you much. However, it really comes in handy when dealing with composed or compound matcher expressions:

adults = Town.find("Springfield").adults
marge  = Character.find("Marge")
bart   = Character.find("Bart")

expect(adults).to include(marge).and exclude(bart)

Thanks to Adam Farhi for helping with the implementation of this feature.

Expectations: Custom matcher chained modifiers now included in generated description

The custom matcher DSL allows you to define a fluent interface using chain:

RSpec::Matchers.define :be_smaller_than do |max|
  chain :and_bigger_than do |min|
    @min = min
  end

  match do |actual|
    actual < max && actual > @min
  end
end

# usage:
expect(10).to be_smaller_than(20).and_bigger_than(5)

In RSpec 2.x and 3.0, the chained part was not included in failure messages:

Failure/Error: expect(5).to be_smaller_than(10).and_bigger_than(7)
  expected 5 to be smaller than 10

RSpec 3.1 can include the chained part in the failure message:

Failure/Error: expect(5).to be_smaller_than(10).and_bigger_than(7)
  expected 5 to be smaller than 10 and bigger than 7

…but only if you enable this behavior with a config option:

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end
end

This config option defaults to false for backwards compatibility. We plan to always enable it in RSpec 4.

Thanks to Dan Oved for implementing this improvement!

Mocks: New *_spy methods

RSpec 2.14 added support for using test doubles as spies, which allow you to set an expectation that a message was received after the fact:

spy = double(:foo => nil)
# do something with spy
expect(spy).to have_received(:foo)

Note that we stubbed foo here. This is necessary because doubles are strict by default – meaning that they will raise an error when they receive an unexpected message. Unfortunately, this forces a bit of duplication on you as you have to declare the method twice (stubbing it once before and expecting it was received after).

You can get around this by using as_null_object which makes a double “loose” rather than strict, allowing it to receive any message:

spy = double.as_null_object
# do something with spy
expect(spy).to have_received(:foo)

This pattern is useful enough that in RSpec 3.1, we’ve added new methods to declare spies:

spy(...)          # equivalent to double(...).as_null_object
instance_spy(...) # equivalent to instance_double(...).as_null_object
class_spy(...)    # equivalent to class_double(...).as_null_object
object_spy(...)   # equivalent to object_double(...).as_null_object

Thanks to Justin Searls for bringing up this issue and Sam Phippen for implementing the new methods.

Mocks: New and_wrap_original API

This new API allows you to easily decorate a particular existing method on a particular object just for the duration of the current example. The original method is yielded to your block as the first argument (before the args of the actual method call).

allow(api_client).to receive(:fetch_users).and_wrap_original do |original_method, *args|
  original_method.call(*args).first(10) # truncate the response to the first 10 users
end

Thanks to Jon Rowe for implementing this feature.

Rails: Rails 4.2 support

rspec-rails 3.1 will officially support Rails 4.2. Aaron Kromer has been doing a great job getting RSpec 3.1 ready for Rails 4.2. This includes a generator provided by Abdelkader Boudih for ActiveJob.

Rails: Generated rails_helper.rb no longer auto-loads spec/support files by default

Aaron has also made a small change to the generator for rails_helper.rb. Previously, it contained some code that would automatically load all files under spec/support. That code is still there but is now commented out. We’ve found that it helps prevent load time bloat to manually require the support files that are needed, rather than always loading all of them.

Of course, if you prefer the convenience of the old way, that’s a reasonable tradeoff, and you can easily uncomment this bit of code.

Stats

rspec-core:

rspec-expectations:

rspec-mocks:

rspec-rails:

rspec-support:

Docs

API Docs

Cucumber Features

Release Notes

rspec-core-3.1.0

Full Changelog

Enhancements:

Bug Fixes:

rspec-expectations-3.1.0

Full Changelog

Enhancements:

Bug Fixes:

rspec-mocks-3.1.0

Full Changelog

Enhancements:

Bugfixes:

rspec-rails-3.1.0

Full Changelog

Enhancements:

Bug Fixes: