RSpec 3.2 has been released!

Myron Marston

Feb 3, 2015

RSpec 3.2 has just been released! Given our commitment to semantic versioning, this should be a trivial upgrade for anyone already using RSpec 3.0 or 3.1, 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 915 commits and 278 merged pull requests from over 50 different contributors!

Thank you to everyone who helped make this release happen!

Notable Changes

Windows CI

RSpec has always supported Windows, but the fact that all the core maintainers develop on POSIX systems has occasionally made that difficult. When RSpec 3.1 was released, we unfortunately broke a couple things on Windows without knowing about it until some users reported the issues (which were later fixed in 3.1.x patch releases). We'd like to prevent that from happening again, so this time around we've put effort into getting Windows CI builds going on AppVeyor.

We have passing Windows builds, but there's still more to do here. If you're an RSpec and Windows user and would like to help us out, please get in touch!

Core: Pending Example Output Includes Failure Details

RSpec 3.0 shipped with new semantics for pending. Rather than skipping the example (now available using skip), pending examples are executed with an expectation that a failure will occur, and, if there is no failure, they'll fail to notify you that they no longer need to be marked as pending. This change is quite useful, as it ensures that every pending example needs to be pending. However, the formatter output didn't indicate the failure that forced the example to remain pending, so you had to un-pend the example to see that detail.

In RSpec 3.2, we've rectified this: pending example output now includes the failure that occurred while executing the example.

Core: Each Example Now Has a Singleton Group

RSpec has a number of metadata-based features that, before now, would only apply to example groups, not individual examples:

RSpec.configure do |config|
  # 1. Filtered module inclusion: examples in groups tagged with `:uses_time`
  #    will have access to the instance methods of `TimeHelpers`
  config.include TimeHelpers, :uses_time

  # 2. Context hooks: a browser will start before groups tagged
  #    with `:uses_browser` and shutdown afterwards.
  config.before(:context, :uses_browser) { Browser.start }
  config.after( :context, :uses_browser) { Browser.shutdown }
end

# 3. Shared context auto-inclusion: groups tagged with
#    `:uses_redis` will have this context included.
RSpec.shared_context "Uses Redis", :uses_redis do
  let(:redis) { Redis.connect(ENV['REDIS_URL']) }
  before { redis.flushdb }
end

In each of these cases, individual examples tagged with the appropriate metadata would not have these constructs applied to them, which could be easy to forget. That's changing in RSpec 3.2: we now treat every example as being implicitly part of a singleton example group of just one example, and that allows us to apply these constructs to individual examples, and not just groups. If you're familiar with Ruby's object model, this may sound familiar – every object in Ruby has a singleton class that holds custom behavior that applies to that instance only. This feature was trivially implemented on top of Ruby's singleton classes.

Core: Performance Improvements

We put some significant effort into optimizing rspec-core's performance for RSpec 3.2. Besides some algorithmic changes and numerous micro-optimizations, we also found ways to greatly reduce the number of object allocations done by rspec-core. According to my benchmarks, RSpec 3.2 allocates ~30% fewer objects than RSpec 3.1, all while gaining numerous new features.

In future releases, I'm hoping we can apply similar improvements to rspec-expectations and rspec-mocks.

Core: New Sandboxing API

RSpec is tested using RSpec. In rspec-core, many of the specs in our spec suite define and run example groups and examples to verify the behavior of how RSpec's various constructs relate when when run in a real RSpec context. To facilitate this dog-fooding, rspec-core's spec suite has long used a sandboxing technique. Each spec is free to mutate configuration and define example groups and examples without worrying about how that could interfere with the RSpec runner's internal state.

This sandboxing is obviously useful for RSpec's own internal use, but it's also useful for testing 3rd party RSpec extensions. We now offer it as a new public API:

# You have to require this file to make this API available...
require 'rspec/core/sandbox'

RSpec.configure do |config|
  config.around do |ex|
    RSpec::Core::Sandbox.sandboxed(&ex)
  end
end

Thanks to Tyler Ball for implementing this.

Core: Shared Example Group Improvements

In this release we fixed several long-standing bugs and annoyances with shared example groups:

If you've had issues with shared example groups in the past due to issues like these, you may want to give them another try.

Expectations: Chain Shorthand For DSL-Defined Custom Matchers

The custom matcher DSL has a chain method that makes it easy to add fluent interfaces to your matchers:

RSpec::Matchers.define :be_bigger_than do |min|
  chain :but_smaller_than do |max|
    @max = max
  end

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

# usage:
expect(10).to be_bigger_than(5).but_smaller_than(15)

As this example shows, the most common use of chain it to accept an additional argument that is used in the match logic somehow. Tom Stuart suggested and implemented an improvement that allows you to shorten the matcher definition to:

RSpec::Matchers.define :be_bigger_than do |min|
  chain :but_smaller_than, :max
  match do |value|
    value > min && value < max
  end
end

The second argument to chain:max – is used to define a max attribute that gets set to the argument passed to but_smaller_than.

Thanks, Tom Stuart!

Expectations: Output Matchers Can Handle Subprocesses

RSpec 3.0 shipped with a new output matcher that allows you to specify expected output to either stdout or stderr:

expect { print 'foo' }.to output('foo').to_stdout
expect { warn  'foo' }.to output(/foo/).to_stderr

The mechanism used for these matchers – temporarily replacing $stdout or $stderr with a StringIO for the duration of the block – is pretty simple but does not work when you spawn subprocesses that output to one of these streams. For example, this fails:

expect { system('echo foo') }.to output("foo\n").to_stdout

In RSpec 3.2, you can replace to_stdout with to_stdout_from_any_process and this expectation will work.

expect { system('echo foo') }.to output("foo\n").to_stdout_from_any_process

This uses an alternate mechanism, where $stdout is temporarily reopened to a temp file, that works with subprocesses. Unfortunately, it's also much, much slower – in our benchmark, it's 30 times slower! For this reason, you have to opt-in to it using to_std(out|err)_from_any_process in place of to_std(out|err).

Thanks to Alex Genco for implementing this improvement!

Expectations: DSL-Defined Custom Matchers Can Now Receive Blocks

When defining a custom matcher using the DSL, there are situations where it would be nice to accept a block:

RSpec::Matchers.define :be_sorted_by do |&blk|
  match do |array|
    array.each_cons(2).all? do |a, b|
      (blk.call(a) <=> blk.call(b)) <= 0
    end
  end
end

# intended usage:
expect(users).to be_sorted_by(&:email)

Unfortunately, Ruby restrictions do not allow us to support this. The define block is executed using class_exec, which ensures it is evaluated in the context of a new matcher class, while also allowing us to forward arguments from the call site to the define block. The block passed to class_exec is the one to be evaluated (in this case, the define block) and there is no way to pass two blocks to class_exec.

In prior versions of RSpec, the block passed to be_sorted_by would be silently ignored. In RSpec 3.2, we now issue a warning:

WARNING: Your `be_sorted_by` custom matcher receives a block argument (`blk`), but
due to limitations in ruby, RSpec cannot provide the block. Instead, use the
`block_arg` method to access the block. Called from path/to/file.rb:line_number.

…which tells you an alternate way to accomplish this: the new block_arg method, available from within a custom matcher:

RSpec::Matchers.define :be_sorted_by do
  match do |array|
    array.each_cons(2).all? do |a, b|
      (block_arg.call(a) <=> block_arg.call(b)) <= 0
    end
  end
end

Thanks to Mike Dalton for implementing this improvement.

Mocks: any_args Works as an Arg Splat

RSpec has had an any_args matcher for a long time:

expect(test_double).to receive(:message).with(any_args)

The any_args matcher behaves exactly like it reads: it allows any arguments (including none) to match the message expectation. In RSpec 3.1 and before, any_args could only be used as a stand-alone argument to with. In RSpec 3.2, we treat it as an arg splat, so you can now use it anywhere in an argument list:

expect(test_double).to receive(:message).with(1, 2, any_args)

This would match calls like test_double.message(1, 2) or test_double.message(1, 2, 3, 4).

Mocks: Mismatched Args Are Now Diffed

A big part of what has made RSpec's failure output so useful is the diff that you get for failures from particular matchers. However, message expectation failures have never included a diff. For example, consider this failing message expectation:

test_double = double
expect(test_double).to receive(:foo).with(%w[ 1 2 3 4 ].join("\n"))
test_double.foo(%w[ 1 2 5 4 ].join("\n"))

In RSpec 3.1 and before, this failed with:

Failure/Error: test_double.foo(%w[ 1 2 5 4 ].join("\n"))
  Double received :foo with unexpected arguments
    expected: ("1\n2\n3\n4")
         got: ("1\n2\n5\n4")

RSpec 3.2 now includes diffs in message expectation failures when appropriate (generally when multi-line strings are involved or when the pretty-print output of the objects are multi-line). In 3.2, this fails with:

Failure/Error: test_double.foo(%w[ 1 2 5 4 ].join("\n"))
  Double received :foo with unexpected arguments
    expected: ("1\n2\n3\n4")
         got: ("1\n2\n5\n4")
  Diff:
  @@ -1,5 +1,5 @@
   1
   2
  -3
  +5
   4

Thanks to Pete Higgins for originally suggesting this feature, and for extracting our differ from rspec-expectation to rspec-support, and thanks to Sam Phippen for updating rspec-mocks to use the newly available differ.

Mocks: Verifying Doubles Can Be Named

RSpec's double has always supported an optional name, which gets used in message expectation failures. In RSpec 3.0, we added some new test double types – instance_double, class_double and object_double – and in 3.1, we added instance_spy, class_spy and object_spy…but we forgot to support an optional name for these types. In RSpec 3.2, these double types now support an optional name. Just pass a second argument (after the interface argument, but before any stubs):

book_1 = instance_double(Book, "The Brothers Karamozov", author: "Fyodor Dostoyevsky")
book_2 = instance_double(Book, "Lord of the Rings", author: "J.R.R. Tolkien")

Thanks to Cezary Baginski for implementing this.

Rails: Instance Doubles Support Dynamic Column Methods Defined by ActiveRecord

ActiveRecord defines a number of methods on your model class based on the column schema of the underlying table. This happens dynamically the first time you call one of these methods. Unfortunately, this led to confusing behavior when using an ActiveRecord-based instance_double: since User.method_defined?(:email) returned false until a column method was called for the first time, instance_double(User) would initially not allow email to be stubbed until the column methods had been dynamically defined.

We documented this issue but it was still a source of frequent confusion with users. In RSpec 3.2, we've addressed this – we now force the model to define the column methods when an ActiveRecord-based instance_double is created so that the verified double works as expected.

Thanks to Jon Rowe for implementing this improvement!

Rails: Support Ruby 2.2 with Rails 3.2 and 4.x

Ruby 2.2 was released in December, and while most Ruby 2.1 code bases work just fine on 2.2, there were a few changes the Rails core team had to make to Rails to support 2.2. Likewise, Aaron Kromer has updated rspec-rails to support Ruby 2.2 on Rails 3.2 and Rails 4.x.

Rails: New Generator for ActionMailer Previews

ActionMailer previews were one of the new features in Rails 4.1. By default, Rails generates the preview classes in your test directory. Since RSpec projects use spec instead of test, this didn't integrate well with rspec-rails.

In 3.2, rspec-rails now ships with a generator that will put ActionMailer preview classes in your spec directory, providing the same functionality that Rails already provides to Test::Unit and Minitest users.

Thanks to Takashi Nakagawa for the initial implementation and to Aaron Kromer for further improvements.

Stats

Combined:

rspec-core:

rspec-expectations:

rspec-mocks:

rspec-rails:

rspec-support:

Docs

API Docs

Cucumber Features

Release Notes

rspec-core-3.2.0

Full Changelog

Enhancements:

Bug Fixes:

rspec-expectations-3.2.0

Full Changelog

Enhancements:

Bug Fixes:

rspec-mocks-3.2.0

Full Changelog

Enhancements:

Bug Fixes:

rspec-rails-3.2.0

Full Changelog

Enhancements:

Bug Fixes:

rspec-support-3.2.0

Full Changelog

Enhancements:

Bug Fixes: