David Chelimsky

random thoughtlessness

Specifying mixins with shared example groups in RSpec-2

| Comments

One question that comes up on the rspec-users mailing list / google group is: “How do I specify modules that get mixed into other modules and classes?”

This is a great question and, naturally, leads to a wide variety of answers depending on context. I’m going to approach this generally, and explain my viewpoint about it, but keep in mind that context is everything, and YMMV. That said:

In theory

With a tool like RSpec, the goal is to specify responsibilities of objects from the perspective of their consumers. Consider this structure:

module M
end

class C
 include M
end

If module M is included in class C, consumers of class C have no reason to know that module M is involved. They just care about the behaviour. Same is true of classes A, B, and D, if they each include module M. Keeping in mind that each host class/module/object (those that include or extend M) can override any of the behaviour of M, each host should be specified independently.

Additionally, if module M enforces some rule, like host objects (i.e. classes and modules that include or extend M) must implement method F, then that responsibility belongs to M, and should be specified in the context of M, not any of its host classes/objects. These rules can be further broken down into rules enforced at mix-in time and rules enforced at runtime.

So we’re interested in specifying two fundamentally different things

  • the behaviour of each class/object that mixes in M in response to events triggered by their consumers
  • the behaviour of M in response to being mixed-in

In practice

Specifying the behaviour of a module in response to being mixed in

Imagine we are developing a module that exposes a bunch of methods related to a person’s age: can_vote?, can_drink?, etc. For this to work, the host object needs to supply the birthdate of the person in question. These sorts of requirements are often documented for us by library providers, but less often required programatically. It would be nice to provide a clear message to the developer when

For this, I’ll typically mix M into anonymous classes and objects and specify what happens:

describe AgeBasedApprovable do
  it "requires host object to provide a 'birthdate' method" do
    host = Object.new
    expect do
      host.extend(AgeBasedApprovable)
    end.to raise_error(/Objects that extend AgeBasedApprovable must provide a 'birthdate' method/)
  end
end

Specifying the behaviour of host classes/objects

For this, I’ve used a combination of shared example groups and custom macros in the past, but the macros are not necessary any longer. Thanks to some lively discussion [1-5], and code from Wincent Colaiuta, Ashley Moran and Myron Marston, shared example groups just got awesome in rspec-2.0! They can now be parameterized and/or customized in three different ways. The biggest change came from having it_should_behave_like (and its new alias, it_behaves_like), generate a nested example group instead of mixing a module directly into the host group. This means that this:

shared_examples_for M do
  it "does something" do
    # ....
  end
end

describe C do
  it_behaves_like M
end

… is equivalent to this:

describe C do
  context "behaves like M" do 
    it "does something" do
      # ....
    end
  end
end

In rspec-1, shared groups are modules that get mixed into the host group, which means material defined in the shared group can impact the host group in surprising ways. With this new structure in rspec-2, the nested group is a completely separate group, and the combination of sharing behaviour (through inheritance) and isolating behaviour (through encapsulation) provides power we never had before in RSpec.

Customizing shared example groups

Here are three techniques for customizing shared groups:

Parameterization

describe Host do
  it_should_behave_like M, Host.new
end

Here, the result of Host.new is passed to the shared group as a block parameter, making that value available at the group level (each example group is a class), and the instance level (each example runs in an instance of that class). So …

shared_examples_for M do |host|
  it "can access #{host} in the docstring" do
    host.do_something # it can access the host _in_ the example
  end
end

Methods defined in host group

describe Host do
  let(:foo) { Host.new }
  it_should_behave_like M
end

In this case, the foo() method defined by let() is inherited by the generated nested group, and available within any of the examples defined in the shared group.

shared_examples_for M do
  it "does something" do
    foo
  end
end

NOTE that instance methods that are inherited like this are not available in the class scope of the generated example group, and are therefore not available for use in docstings:

shared_examples_for M do
  it "does some #{foo}" do # this would raise an error
    # ...
  end
end

Methods defined in an extension block

describe Host do
  it_should_behave_like M do
    let(:foo) { Host.new }
  end
end

The block passed to it_should_behave_like() is eval’d after the shared group is eval’d, allowing you to define default implementations of methods in the shared group. This means we can define groups that programmatically enforce rules for the host groups. For example:

shared_examples_for M do
  def foo
    raise "Groups that include shared examples for M must provide a foo method"
  end

  it "does something needing foo" do
    foo
  end
end 

Now library authors can now ship shared groups that will programmatically instruct end users how to use them!

[1] http://github.com/rspec/rspec-core/issues/issue/71

[2] http://github.com/rspec/rspec-core/issues/issue/74

[3] http://groups.google.com/group/rspec/browse_thread/thread/f5620df1c42874bf#

[4] http://groups.google.com/group/rspec/browse_thread/thread/16d553ee2e51ccbd#

[5] http://groups.google.com/group/rspec/browse_thread/thread/a23d5fb84a31f11e#

rspec-2.1 is released

| Comments

rspec-core-2.1.0 / 2010-11-07

Cucumber features

RDoc # will be generated by 2010-11-08

full changelog

  • Enhancments

    • Add skip_bundler option to rake task to tell rake task to ignore the presence of a Gemfile (jfelchner)
    • Add gemfile option to rake task to tell rake task what Gemfile to look for (defaults to ‘Gemfile’)
    • Allow passing caller trace into Metadata to support extensions (Glenn Vanderburg)
    • Add deprecation warning for Spec::Runner.configure to aid upgrade from RSpec-1
    • Add deprecated Spec::Rake::SpecTask to aid upgrade from RSpec-1
    • Add ‘autospec’ command with helpful message to aid upgrade from RSpec-1
    • Add support for filtering with tags on CLI (Lailson Bandeira)
    • Add a helpful message about RUBYOPT when require fails in bin/rspec (slyphon)
    • Add “-Ilib” to the default rcov options (Tianyi Cui)
    • Make the expectation framework configurable (default rspec, of course) (Justin Ko)
    • Add ‘pending’ to be conditional (Myron Marston)
    • Add explicit support for :if and :unless as metadata keys for conditional run of examples (Myron Marston)
    • Add —fail-fast command line option (Jeff Kreeftmeijer)
  • Bug fixes

    • Eliminate stack overflow with “subject { self }”
    • Require ‘rspec/core’ in the Raketask (ensures it required when running rcov)

rspec-expectations-2.1.0 / 2010-11-07

Cucumber features

RDoc # will be generated by 2010-11-08

full changelog

  • Enhancements

    • be_within(delta).of(expected) matcher (Myron Marston)
    • Lots of new Cucumber features (Myron Marston)
    • Raise error if you try “should != expected” on Ruby-1.9 (Myron Marston)
    • Improved failure messages from throw_symbol (Myron Marston)
  • Bug fixes

    • Eliminate hard dependency on RSpec::Core (Myron Marston)
    • have_matcher – use pluralize only when ActiveSupport inflections are indeed defined (Josep M Bach)
    • throw_symbol matcher no longer swallows exceptions (Myron Marston)
    • fix matcher chaining to avoid name collisions (Myron Marston)

rspec-mocks-2.1.0 / 2010-11-07

Cucumber features

RDoc # will be generated by 2010-11-08

full changelog

  • Bug fixes
    • Fix serialization of stubbed object (Josep M Bach)

rspec-rails-2.1.0 / 2010-11-07

Cucumber features

RDoc # will be generated by 2010-11-08

full changelog

  • Enhancements

    • Move errors_on to ActiveModel to support other AM-compliant ORMs
  • Bug fixes

    • Check for presence of ActiveRecord instead of checking Rails config (gets rspec out of the way of multiple ORMs in the same app)

rspec-2.0.1 is released!

| Comments

This is primarily a bug-fix release for rspec-core:

rspec-core-2.0.1

full changelog

  • Bug fixes
    • restore color when using spork + autotest
    • Pending examples without docstrings render the correct message (Josep M. Bach)
    • Fixed bug where a failure in a spec file ending in anything but _spec.rb would fail in a confusing way.
    • Support backtrace lines from erb templates in html formatter (Alex Crichton)

rspec-expectations-2.0.1

full changelog

  • Enhancements
    • Make dependencies on other rspec gems consistent across gems

rspec-mocks-2.0.1

full changelog

  • Enhancements
    • Make dependencies on other rspec gems consistent across gems

rspec-rails-2.0.1 is released!

| Comments

The rails-3.0.1 release excluded a change that I had naively expected to be included. This upgrade is only necessary if you write view specs and are upgrading to rails-3.0.1. To upgrade, all you need to do is change your Gemfile to read:

gem "rspec-rails", "2.0.1"

And then run

bundle update rspec-rails

Release Notes

2.0.1 / 2010-10-15

full changelog

  • Enhancements

    • Add option to not generate request spec (—skip-request-specs)
  • Bug fixes

    • Updated the mock_[model] method generated in controller specs so it adds any stubs submitted each time it is called.
    • Fixed bug where view assigns weren’t making it to the view in view specs in Rails-3.0.1. (Emanuele Vicentini)

RSpec-2.0.0 is released!

| Comments

This marks the end of a year-long effort that improves RSpec in a number of ways, including modularity, cleaner code, and much better integration with Rails-3 than was possible before.

Docs, with a little bit of relish

In addition to the documentation available at all the places mentioned my earlier post, we’ve also got all of the Cucumber features posted to Justin Ko’s new Cucumber presentation app, relish.

http://relishapp.com/rspec

We’ll also have the RDoc up on http://rdoc.info in a day or so.

Thanks!

Big thanks to 80+ contributors who submitted patches for RSpec-2.0.0, including [1]:

Aan, Adam Walters, Akira Matsuda, Alex Crichton, Anderson Dias, Andre Arko, Andreas Neuhaus, Ashley Moran, Ben Armston, Ben Rady, Brasten Sager, Brian J Reath, Carlhuda, Chad Humphries, Charles Lowell, Chris Redinger, Chuck Remes, Corey Ehmke, Corey Haines, Dan Peterson, Dave Newman, David Genord II, David S. Kang, Ethan Gunderson, Gonçalo Silva, Greg Sterndale, Hans de Graaff, Iain Hecker, Jacques Crocker, Jean-Daniel Guyot, Jeff Ramnani, Jim Breen, Johan Kiviniemi, Josep Mª Bach, Josh Graham, Joshua Nichols, Kabari Hendrick, Kristian M, Lailson B, Len Smith, Leonardo Bessa, Les Hill, Luis Lavena, Marcin Kulik, Markus Schirp, Matt Remsik, Matt Yoho, Matthew Todd, Michael Niessner, Mike Gehard, Myron Marston, Nate Jackson, Neeraj Singh, Nestor Ovroy, Nick Ang, Nicolas Braem, Paul Rosania, Phil Smith, Postmodern, Prasad, Rob Sanheim, Roman Chernyatchik, Ryan Bigg, Ryan Briones, Sam Pohlenz, Scott Taylor, Shin-ichiro OGAWA, Thibaud Guillaume-Gentil, Tim Connor, Tim Harper, Tom Stuart, Vít Ondruch, Wincent Colaiuta, aslakhellesoy, eira, garren smith, grosser, hasimo, justinko, rup, speedmax, wycats

Extra special thanks go to:

  • Chad Humphries for contributing his Micronaut gem which is the basis for rspec-core-2

  • Yehuda Katz, Carl Lerche, and José Valim, for their assistance with getting rspec-rails-2 to take advantage of new APIs in Rails-3, and for shepherding patches to Rails that made it far simpler for testing extensions like rspec-rails to hook into Rails’ testing infrastructure. Their work here has significantly reduced the risk that Rails point-releases will break rspec-rails.

  • Myron Marston for a wealth of thoughtful contributions including Cucumber features that we can all learn from

  • Justin Ko for his direct contributions to rspec, and for relish, which makes executable documentation act more like documentation.

What’s next?

rspec-rails-2 for rails-2

There are a couple of projects floating around that support rspec-2 and rails-2. I haven’t had the chance to review any of these myself, but my hope is that we’ll have be an official rspec-2 for rails-2 gem in the coming months.

rspec-1 maintenance

rspec-1 will continue to get maintenance releases, but these will be restricted, primarily, to bug fixes. Any new features will go into rspec-2, and will likely not be back-ported.

[1] Contributor names were generated from the git commit logs.

rspec-1.3.1 / rspec-rails-1.3.3 are released

| Comments

I just released rspec-1.3.1 and rspec-rails-1.3.3.

These are mostly bug fixes that have been sitting around for all to long as I focused on rspec-2 (coming very soon).

Report issues for rspec[-rails]-1.x to https://rspec.lighthouseapp.com/projects/5645.

Docs:

http://rspec.info/

http://rdoc.info/gems/rspec/1.3.1/frames

http://rdoc.info/gems/rspec-rails/1.3.3/frames

Cheers, David

rspec-1.3.1 / 2010-10-09

  • enhancements
  • Array =~ matcher works with subclasses of Array (Matthew Peychich & Pat Maddox)
  • config.suppress_deprecation_warnings!

  • bug fixes

  • QuitBacktraceTweaker no longer eats all paths with ‘lib’ (Tim Harper – #912)
  • Fix delegation of stubbed values on superclass class-level methods. (Scott Taylor – #496 – #957)
  • Fix pending to work with ruby-1.9

  • deprecations

  • share_as (will be removed from rspec-core-2.0)
  • simple_matcher (will be removed from rspec-core-2.0)

rspec-rails-1.3.3 / 2010-10-09

  • enhancements
  • replace use of ‘returning’ with ‘tap’

  • bug fixes

  • support message expectation on template.render with locals (Sergey Nebolsin). Closes #941.
  • helper instance variable no longer persists across examples (alex rothenberg). Closes #627.
  • mock_model stubs marked_for_destruction? (returns false).

RSpec-2.0.0.rc is released!

| Comments

See http://blog.davidchelimsky.net/2010/07/01/rspec-2-documentation for links to all sorts of documentation on rspec-2.

Plan is to release rspec-2.0.0 (final) within the next week, so please install, upgrade, etc, and report issues to:

http://github.com/rspec/rspec-core/issues

http://github.com/rspec/rspec-expectations/issues

http://github.com/rspec/rspec-mocks/issues

http://github.com/rspec/rspec-rails/issues

Many thinks to all of the contributors who got us here!

rspec-core-2.0.0.rc / 2010-10-05

full changelog

  • Enhancements

    • implicitly require unknown formatters so you don’t have to require the file explicitly on the commmand line (Michael Grosser)
    • add —out/-o option to assign output target
    • added fail_fast configuration option to abort on first failure
    • support a Hash subject (its([:key]) { should == value }) (Josep M. Bach)
  • Bug fixes

    • Explicitly require rspec version to fix broken rdoc task (Hans de Graaff)
    • Ignore backtrace lines that come from other languages, like Java or Javascript (Charles Lowell)
    • Rake task now does what is expected when setting (or not setting) fail_on_error and verbose
    • Fix bug in which before/after(:all) hooks were running on excluded nested groups (Myron Marston)
    • Fix before(:all) error handling so that it fails examples in nested groups, too (Myron Marston)

rspec-expectations-2.0.0.rc / 2010-10-05

full changelog

  • Enhancements

    • require ‘rspec/expectations’ in a T::U or MiniUnit suite (Josep M. Bach)
  • Bug fixes

    • change by 0 passes/fails correctly (Len Smith)
    • Add description to satisfy matcher

rspec-mocks-2.0.0.rc / 2010-10-05

full changelog

  • Enhancements

    • support passing a block to an expecttation block (Nicolas Braem)
      • obj.should_receive(:msg) {|█| … }
  • Bug fixes

    • Fix YAML serialization of stub (Myron Marston)
    • Fix rdoc rake task (Hans de Graaff)

rspec-rails-2.0.0.rc / 2010-10-05

full changelog

  • Enhancements
    • add —webrat-matchers flag to scaffold generator (for view specs)
    • separate ActiveModel and ActiveRecord APIs in mock_model and stub_model
    • ControllerExampleGroup uses controller as the implicit subject by default (Paul Rosania)

RSpec-2.0.0.beta.22 is released!

| Comments

We’re getting very close to a 2.0 release candidate, so if you’re not already using rspec-2 (with or without rails-3), now is the time to start. I need your feedback, so from here on in I’ll be sending out announcements and release notes for each beta release.

As for rspec-2 with rails-2, there are a few efforts underway to make that work, but that will be in the form of a separate gem and our priority is getting rspec-2 out the door.

Please report issues or submit pull requests (yes, pull requests are fine now that github has integrated them so well with issues) to the appropriate repos:

Here are release notes for each gem in this beta release, drawn from the nascent History.md files in each project.

rspec-core-2.0.0.beta.22 / 2010-09-12

full changelog

  • Enhancements

    • removed at_exit hook
    • CTRL-C stops the run (almost) immediately
      • first it cleans things up by running the appropriate after(:all) and after(:suite) hooks
      • then it reports on any examples that have already run
    • cleaned up rake task
      • generate correct task under variety of conditions
      • options are more consistent
      • deprecated redundant options
    • run ‘bundle exec autotest’ when Gemfile is present
    • support ERB in .rspec options files (Justin Ko)
    • depend on bundler for development tasks (Myron Marston)
    • add example_group_finished to formatters and reporter (Roman Chernyatchik)
  • Bug fixes

    • support paths with spaces when using autotest (Andreas Neuhaus)
    • fix module_exec with ruby 1.8.6 (Myron Marston)
    • remove context method from top-level
      • was conflicting with irb, for example
    • errors in before(:all) are now reported correctly (Chad Humphries)
  • Removals

    • removed -o —options-file command line option
      • use ./.rspec and ~/.rspec

rspec-expectations-2.0.0.beta.22 / 2010-09-12

full changelog

  • Enhancements

    • diffing improvements
      • diff multiline strings
      • don’t diff single line strings
      • don’t diff numbers (silly)
      • diff regexp + multiline string
  • Bug fixes

    • should[_not] change now handles boolean values correctly

rspec-mocks-2.0.0.beta.22 / 2010-09-12

full changelog

  • Bug fixes
    • fixed regression that broke obj.stub_chain(:a, :b => :c)
    • fixed regression that broke obj.stub_chain(:a, :b) { :c }
    • respond_to? always returns true when using as_null_object

2.0.0.beta.22 / 2010-09-12

full changelog

  • Enhancements

    • autotest mapping improvements (Andreas Neuhaus)
  • Bug fixes

    • delegate flunk to assertion delegate

The RSpec Book has entered the production process!

| Comments

I’m thrilled to announce that The RSpec Book has entered the production process!

For those of you unfamiliar with the publishing industry, as I was before this project, “has entered the production process” does not mean that it’s off to the printer. What it does mean is that it is currently being indexed so readers will be able to find the stuff they’re looking for. After indexing it will be copyedited (in which someone with better grammar and spelling than any of the authors possess makes the book more readable) and typeset, and then off to the printer.

If all goes to plan (yes, there actually is a plan!), books.should be\_on\_shelves in late September, early October.

That light at the end of the tunnel is, finally, not an oncoming train!