Update: There’s a Japanese translation of this available now.
RSpec 2.0 was released in October 2010. In the nearly three years since then, we’ve been able to continually improve RSpec without needing to make breaking changes, but we’ve reached a point where RSpec has a fair bit of cruft stemming from the need to retain backwards compatibility with older 2.x releases.
RSpec 2.14 will be the last RSpec 2 feature release. (We may do some bug-fix patch releases, though). We’re getting started on RSpec 3, and I’d like to share our thoughts on the direction RSpec will be going.
None of this is set in stone, of course, and ultimately RSpec has been a successful project because of all of the people who use it. So please speak up if you have any thoughts about the direction we should take with RSpec 3!
What’s Being Removed
No More 1.8.6 and 1.9.1 Support
RSpec 2.x has continued to support Ruby 1.8.6 long after that version has ceased being supported by the MRI team. As an important piece of testing infrastructure in the Ruby ecosystem, we’ve felt that it’s important to allow gem authors to decide when to drop support for older Ruby versions, and not be forced to do so prematurely because the test framework they’ve chosen to use no longer supports one of the versions they support.
Ruby 1.8.6 and 1.9.1 have not been available on Travis for nearly two years, and without the safety net of a CI server running our builds on those versions, it’s become extremely difficult to continue supporting them. In practice, we’ve really only “semi-supported” these ruby versions for the last couple years: when users report issues on these ruby versions, we’ll fix them, but we haven’t been expending effort on support beyond that.
And so, the time has come to drop support for these versions. We plan to continue to support 1.8.7, 1.9.2 and all newer ruby versions on RSpec 3. Given that 1.8.7 is now legacy, we’ll probably be dropping support for 1.8.7 in RSpec 4, although if/when Travis stops supporting it before then, we’ll only be able to “semi-support” it in the same way we’ve been semi-supporting 1.8.6.
its will be moved into an external gem
I’ve written about this before, so I won’t belabor the point here. We plan to move
its out of rspec-core and into an external gem.
have(x).items matchers will be moved into an external gem
RSpec originated in a time before the existence of Cucumber/Gherkin, and one of its early goals was to express things in natural language that a project stakeholder could understand. In those early days, an expression like
team.should have(9).players made sense for the goals of the project. Since then, Cucumber/Gherkin have emerged as a better alternative for stakeholder-focused tests, and RSpec is rarely used for that purpose today. The
have(x).items family of matchers (including the
have_at_most(x).items siblings) are unnecessarily complicated, when a simple expression like
expect(team.players.size).to eq(9) works just fine.
We plan to move these matchers out of
rspec-expectations and into an external gem.
Core: No more explicit debugger support
RSpec has long supported a
--debug command line option for enabling the debugger via the
ruby-debug gem. However, today
ruby-debug is not the only (or even main) debugging gem in use today. debugger has become the de-facto standard debugging gem for MRI 1.9.2+, and many developers prefer to use pry for debugging. Other ruby interpreters like Rubinius feature their own debugger.
We plan to remove the explicit debugger support in RSpec 3. Besides removing the command line option, we’ll be removing the monkey patching of debugger in Kernel when ruby-debug is not loaded, so you will get a
debugger when a debugger is not loaded.
If you want to continue to load the debugger using a command line option, you can use the require flag (
-r), using an option like
Core: No more RCov integration
RSpec::Core::RakeTask has had some RCov options for a long time. RCov only works with MRI 1.8, and today most ruby developers use SimpleCov for their code coverage needs. SimpleCov integrates with RSpec (or any test framework) very simply, with no explicit support needed from within RSpec itself.
Core: Autotest integration will be moved into an external gem
Autotest used to be the primary ruby continuous test runner. These days, guard seems to be the more popular choice, and there’s no reason that RSpec’s Autotest integration needs to remain in rspec-core.
Core: TextMate formatter will be moved into the TextMate bundle
For many years, TextMate was the most popular text editor used by ruby developers. RSpec has had a TextMate-specific formatter for many years. These days, TextMate isn’t nearly as popular among ruby developers as it used to be, and there’s no compelling reason for the TextMate formatter to remain in rspec-core.
Lots of Deprecations
RSpec 2.14 includes many things that have been deprecated over the last couple of years. We plan to remove nearly all of the deprecated APIs and features.
What about the old expectation/mock syntax?
RSpec 2.11 introduced a new
expect-based syntax for rspec-expectations. In RSpec 2.14, we updated rspec-mocks to use a similar syntax. Since introducing the new syntax, I’ve received a number of questions about how soon we will be deprecating or removing the old
While I won’t say “never” (who knows what the future holds?), we don’t have any current plans to ever remove the old syntax. Users have invested in code that uses the old syntax for many years, and while we recommend using the new syntax (particularly for new projects), we’d be doing users a disservice to remove the old syntax anytime soon. It’s also not a significant maintenance burden.
For RSpec 3, we considered the idea of disabling the old syntax by default, forcing users to opt-in to use it. However, I think that doing so would be a disservice to new users who are coming to RSpec through a less-than-current tutorial. Getting a
NoMethodError for an example copied from a tutorial can be very frustrating to someone trying RSpec for the first time. Experienced users can easily disable the old syntax, whereas new users aren’t likely to have enough RSpec knowledge to know to enable the old syntax used by their tutorial.
That said, we do want to encourage people to switch to the new syntax, so we plan to make RSpec 3 print a warning on first usage of any the old syntax methods (
should_receive, etc) unless the
should syntax has been explicitly enabled. This should nudge folks towards the new syntax while keeping RSpec friendly to new users and will pave the way for the old syntax to be disabled by default in RSpec 4.
Zero Monkey Patching Mode!
Historically, RSpec has extensively used monkey patching to create its readable syntax, adding methods like
stub to every object. In the last few 2.x releases, we’ve worked towards reducing the amount of monkey patching done by RSpec:
- As of rspec-core 2.11,
describeis no longer added to every object. Instead, it is only added to the top-level
mainobject and to
Module(so that it is available from within classes and modules).
- In rspec-expectations 2.11, we added the
expectsyntax and provided a config option to disable the
shouldsyntax – which removes
should_notfrom every object.
- As of rspec-core 2.12,
shared_context, are no longer added to every object. As with
describe, they are only added to the top-level
mainobject and to
- In rspec-mocks 2.14, we updated
rspec-mocksto support an
expect-based syntax as well, and provided a config option to disable the old mocking syntax – which removes
stubfrom every object.
As discussed above, we’ll be removing RSpec’s monkey-patched
Kernel#debugger in 3.0. We’re also planning to provide a config option to remove the monkey patching of the top-level DSL methods (
shared_examples_for, etc) onto
Module, instead requiring you to prefix these methods with
The net result will be a set of config options (one for
rspec-expectations, one for
rspec-mocks and one for
rspec-core), that will provide a zero-monkey-patching mode for RSpec. (We may also provide a single unified config option that sets all three).
We plan for these config options to become the defaults in RSpec 4.0, so that RSpec 4.0 will have zero monkey patching out of the box.
Mocks: Test double interface verification
It’s unfortunately been very easy to let your test doubles get out of sync with the real interfaces they are doubling. When you rename a method, or change the number of arguments a method expects, it’s easy to forget to update the test doubles you are using as standins for the changed class.
I’ve long been a fan of rspec-fire’s approach to solving this problem. I plan to port a version of it to rspec-mocks.
Take a look at the github issue where we are discussing this for the full details (the API and semantics of this feature are certainly not set in stone yet, so please voice your thoughts on that ticket!)
Expectations: Fully composable matchers
In RSpec 2.13, we added support for the
include matcher to accept a list of matchers to match against. This kind of composability is quite useful and we plan to extend it to all matchers in RSpec 3. For example, you could use an expression like:
This expresses a detailed expectation: “I expect
some_object.do_something to yield with a collection that includes a string matching
/foo/ and a string matching
We’re also considering adding matcher aliases that read better when composed in this fashion, so that you could write this as:
For more details, or to weigh in on this issue, take a look at the github issue.
Core: Formatter API improvements
The current API for notifying formatters of test suite progress has proved to be a bit inflexible when it comes to adding in new notifications and changing existing notifications. We’re planning to change it in a couple ways:
- Rather than requiring that all formatters implement all notification methods, formatters will be able to subscribe to specific notifications. This will allow you to implement the minimum set your formatter needs, and will allow us to add new notifications without worrying about breaking existing formatters.
- Notification arguments will change from an ordered list of arguments to a single value object, which will allow us to easily add additional data to specific notifications without changing the method signature.
These changes will allow us to make further improvements that we have been unable to make in the 2.x releases. We also plan to provide a compatibility layer in RSpec 3 that wraps formatters written against the old API and adapts them to the new API so that users can more easily upgrade when they rely upon old formatters.
For more details, take a look at the github issue.
Core: DSL methods will yield the example
In RSpec 2, the current running example is exposed as
example. It can be used to access the example’s metadata. This has occasionally caused problems when users inadvertently define their own
example method. In RSpec 3, we’re removing the
example method, opting to yield the example from each DSL method that runs in the context of an example:
We’re aware that this may cause upgrade headaches for users who rely on gems that use the
example API (such as Capybara). We’re discussing ways to make the upgrade smoother, both for users of gem authors. For more information, see the github issue.
Expectations: Matcher protocol and custom matcher API changes
While RSpec has been moving away from its
should-based syntax, the matcher protocol and custom matcher API have not changed accordingly. The matcher protocol still relies upon methods like
failure_message_for_should_not, and the custom matcher API has methods like
In RSpec 3, we’d like to change the matcher protocol and custom matcher API to no longer speak in terms of
should while still retaining a backwards compatibility layer so that existing matchers will continue to work, with the plan to remove that compatibility layer in RSpec 4. We’re not sure what the new APIs will be yet; if you have thoughts, please chime in on the github issue.
any_instance block implementations will yield the receiver
When stubbing a method using
any_instance you can pass a block implementation just like a normal stub. However, if you wanted to access the receiver (i.e. the instance receiving the message) in the block, there was no way to accomplish this. In RSpec 3, we’re correcting this oversight, and the receiver will be yielded as the first block argument:
For backwards compatibility, we’ll be adding a config option to disable this behavior.
The Upgrade Path
Even though RSpec 3.0 will be a major release that allows us to make intentional breaking changes for the first time since 2010, it’s important to us that the upgrade path for existing test suites be as easy as possible. To that end, we’re planning a 2.99 release that will exist purely to help users upgrade. Here’s what we have in mind:
- RSpec 2.99 will be RSpec 2.14 plus some extra deprectation warnings for things that will be removed in 3.0.
- You should be able to take your existing RSpec 2.x test suite and upgrade to 2.99 without needing to make any changes.
- RSpec 2.99 will give you deprecation notices for anything we’re removing or somehow breaking in 3.0. You should address those warnings. Once you’re warning-free on 2.99, you should be able to upgrade to 3.0 without needing to make any further changes.
The 2.99 release will be an important step that shouldn’t be skipped in the upgrade process. It’ll give you an upgrade checklist that is specifically tailored to your test suite’s usage of RSpec, giving you a much simpler and more efficient way to upgrade than combing through changelogs trying to figure out what all is changing in RSpec 3.
The Development and Release Plan
We’ve already began working on RSpec 3 in the master branches of each of the RSpec repos. We also have a 2-14-maintenance branch for 2.14 changes (i.e. for possible patch releases) and a 2-99-maintenance branch for the changes that will be going in to 2.99. We’re planning to do multiple release candidates (and potentially some beta releases) as we make progress towards the final 3.0 release.
I won’t venture a guess for when we might release RSpec 3. Experience has taught me that software release date estimates are always wrong :(.
“How can I help?”
The current RSpec core team (David, Andy, Jon, Sam, Bradley and myself) will be driving the work for the 3.0 release…but as always, we love to get help from the community. Here are some specific ways you can help out:
- As mentioned above, we plan to extract some of RSpec’s functionality into an external gem (
havematchers and the autotest integration). It would be great to find folks in the community who can step forward and take over maintenance (and maybe even do the initial extraction, if you’re so inclined) for these gems.
- We value community feedback about the direction RSpec is going, so please comment on the issues I’ve linked to above (and any other issues, really – there will be more in RSpec 3 than I’ve covered here!) if you have thoughts or ideas.
- If you’d like to help contribute code to RSpec, that’s great! However, during this period, there’s going to be more churn than usual in the codebases of the various RSpec gems, creating more merge conflicts with pull requests, and making it more difficult for us to integrate contributions from users. So, if you want to contribute, please get in touch (either by commenting on github issues and/or chatting with us on the #rspec channel on irc.freenode.net) before putting extensive effort into a PR. A little bit of communication up front will go a long way towards making it easy for us to integrate your contribution.
- One of the most important ways you can help is to try out out the pre-release versions of 2.99 and 3.0 and give us feedback. Stay tuned for release announcements.
- rspec.info is badly in need of a refresh, and it would be great to launch a new version of that site in tandem with the release of RSpec 3. However, it’s very hard for the RSpec core team to find time to work on the website, and most of us focus more on backend than frontend dev. It would be great if some community members would step forward and help in this area!