rspec-2.12 is released
November 12th, 2012
rspec-2.12 is a minor release (per SemVer), which includes numerous enhancements and bug fixes. It is fully backward compatible with previous rspec-2 releases and is a recommended upgrade for all users.
Thanks to all who contributed. Special thanks to Myron Marston and Andy Lindeman for their personal contributions to the code as well as a great job shepherding pull requests from several new contributors.
UPDATE: If you’re an rspec/rails/capybara user, be sure to read Andy Lindeman’s blog post on Capybara-2.0 and rspec-rails.
Docs
RDoc
- http://rubydoc.info/gems/rspec-core
- http://rubydoc.info/gems/rspec-expectations
- http://rubydoc.info/gems/rspec-mocks
- http://rubydoc.info/gems/rspec-rails
Cucumber features
- http://relishapp.com/rspec/rspec-core
- http://relishapp.com/rspec/rspec-expectations
- http://relishapp.com/rspec/rspec-mocks
- http://relishapp.com/rspec/rspec-rails
Release notes
rspec-core-2.12.0
Enhancements
- Add support for custom ordering strategies for groups and examples. (Myron Marston)
- JSON Formatter (Alex Chaffee)
- Refactor rake task internals (Sam Phippen)
- Refactor HtmlFormatter (Pete Hodgson)
- Autotest supports a path to Ruby that contains spaces (dsisnero)
- Provide a helpful warning when a shared example group is redefined. (Mark Burns).
--default_pathcan be specified as--default-path.--line_numbercan be specified as--line-number. Hyphens are more idiomatic command line argument separators (Sam Phippen).- A more useful error message is shown when an invalid command line option is used (Jordi Polo).
- Add
format_docstrings { |str| }config option. It can be used to apply formatting rules to example group and example docstrings. (Alex Tan) - Add support for an
.rspec-localoptions file. This is intended to allow individual developers to set options in a git-ignored file that override the common project options in.rspec. (Sam Phippen) - Support for mocha 0.13.0. (Andy Lindeman)
Bug fixes
- Remove override of
ExampleGroup#ancestors. This is a core ruby method that RSpec shouldn’t override. Instead, defineExampleGroup#parent_groups. (Myron Marston) - Limit monkey patching of shared example/context declaration methods
(
shared_examples_for, etc.) to just the objects that need it rather than every object in the system (Myron Marston). - Fix Metadata#fetch to support computed values (Sam Goldman).
- Named subject can now be referred to from within subject block in a nested group (tomykaira).
- Fix
fail_fastso that it properly exits when an error occurs in abefore(:all) hook(Bradley Schaefer). - Make the order spec files are loaded consistent, regardless of the order of the files returned by the OS or the order passed at the command line (Jo Liss and Sam Phippen).
- Ensure instance variables from
before(:all)are always exposed fromafter(:all), even if an error occurs inbefore(:all)(Sam Phippen). rspec --initno longer generates an incorrect warning about--configurebeing deprecated (Sam Phippen).- Fix pluralization of
1 seconds(Odin Dutton) - Fix ANSICON url (Jarmo Pertman)
- Use dup of Time so reporting isn’t clobbered by examples that modify Time without properly restoring it. (David Chelimsky)
Deprecations
share_asis no longer needed.shared_contextand/orRSpec::SharedContextprovide better mechanisms (Sam Phippen).- Deprecate
RSpec.configurationwith a block (useRSpec.configure).
rspec-expectations-2.12.0
Enhancements
- Colorize diffs if the
--coloroption is configured. (Alex Coplan) - Include backtraces in unexpected errors handled by
raise_errormatcher (Myron Marston) - Print a warning when users accidentally pass a non-string argument as an expectation message (Sam Phippen)
=~andmatch_arraymatchers output a more useful error message when the actual value is not an array (or an object that responds to#to_ary) (Sam Phippen)
Bug fixes
- Fix
includematcher so thatexpect({}).to include(:a => nil)fails as it should (Sam Phippen). - Fix
be_an_instance_ofmatcher so thatClass#to_sis used in the description rather thanClass#inspect, since some classes (likeActiveRecord::Base) define a long, verbose#inspect. (Tom Stuart)
rspec-mocks-2.12.0
Enhancements
and_raisecan accept an exception class and message, more closely matchingKernel#raise(e.g.,foo.stub(:bar).and_raise(RuntimeError, "message")) (Bas Vodde)- Add
and_call_original, which will delegate the message to the original method (Myron Marston).
Deprecations:
- Add deprecation warning when using
and_returnwithshould_not_receive(Neha Kumari)
rspec-rails-2.12.0
Enhancements
- Support validation contexts when using
#errors_on(Woody Peterson) - Include RequestExampleGroup in groups in spec/api
Bug fixes
- Add
shouldandshould_nottoCollectionProxy(Rails 3.1+) andAssociationProxy(Rails 3.0). (Myron Marston) controller.controller_pathis set correctly for view specs in Rails 3.1+. (Andy Lindeman)- Generated specs support module namespacing (e.g., in a Rails engine). (Andy Lindeman)
renderproperly infers the view to be rendered in Rails 3.0 and 3.1 (John Firebaugh)- AutoTest mappings involving config/ work correctly (Brent J. Nordquist)
- Failures message for
be_new_recordare more useful (Andy Lindeman)
Agile Testing and BDD eXchange 2012 in NYC
September 22nd, 2012
I’m excited to be presenting on Behavior-Driven Objects at the Agile Testing & BDD eXchange in NYC on Monday, October 1st (details below). From the conference website:
One of the original ideas behind BDD was that testing should be about behavior at all levels. Effectively, all automated tests can be viewed as “functional tests” of entry points, be they user facing or internal, crossing procedural boundaries or not. In this talk we’ll explore approaches to focusing on the behavior of objects, using language that also serves to document the expected behavior in ways useful to both technical and non-technical stakeholders.
The Agile Testing & BDD eXchange is a one-day conference with talks, open-space discussions and focus on ‘quality time’ between presenters and attendees. There is a nice blend of technical talks like Uncle Bob Martin talking about how to apply SOLID principles to test design, and more process oriented talks like Ellen Gottesdiener’s talk on product definition through structured conversation.
You can read the full program and purchase tickets at http://bit.ly/S0Ypb8 (be sure to use the promo-code “bddxnyc-community-discount”).
What: Agile Testing & BDD eXchange NYC
When: October 1st 2012
Where: The Ace Hotel NYC
Twitter: #BDDXNYC
rspec-2.11 is released!
July 7th, 2012
rspec-2.11.0 is out and filled with a bunch of new features. Big thanks to all who contributed, especially Justin Ko, Andy Lindeman (the newest addition to the RSpec core team) and Myron Marston for their great job addressing issues and shepherding pull requests.
Thanks also to Myron for all his work on two great new features: the new expectation syntax and support for stubbing constants.
rspec-core-2.11.0
Enhancements
- Support multiple
--exampleoptions. (Daniel Doubrovkine @dblock) - Named subject e.g.
subject(:article) { Article.new }- see http://blog.davidchelimsky.net/2012/05/13/spec-smell-explicit-use-of-subject/ for background.
- thanks to Bradley Schaefer for suggesting it and Avdi Grimm for almost suggesting it.
config.mock_withandconfig.expect_withyield custom config object to a block if given- aids decoupling from rspec-core’s configuation
include_contextandinclude_examplessupport a block, which gets eval’d in the current context (vs the nested context generated byit_behaves_like).- Add
config.order = 'random'to thespec_helper.rbgenerated byrspec --init. - Delay the loading of DRb (Myron Marston).
- Limit monkey patching of
describeonto just the objects that need it rather than every object in the system (Myron Marston).
Bug fixes
- Support alternative path separators. For example, on Windows, you can now do
this:
rspec spec\subdir. (Jarmo Pertman @jarmo) - When an example raises an error and an after or around hook does as well, print out the hook error. Previously, the error was silenced and the user got no feedback about what happened. (Myron Marston)
--requireand-Iare merged among different configuration sources (Andy Lindeman)- Delegate to mocha methods instead of aliasing them in mocha adapter.
rspec-expectations-2.11.0
Enhancements
- Expand
expectsyntax so that it supports expections on bare values in addition to blocks (Myron Marston). - Add configuration options to control available expectation syntaxes
(Myron Marston):
RSpec.configuration.expect_with(:rspec) { |c| c.syntax = :expect }RSpec.configuration.expect_with(:rspec) { |c| c.syntax = :should }RSpec.configuration.expect_with(:rspec) { |c| c.syntax = [:should, :expect] }RSpec.configuration.add_should_and_should_not_to Delegator
Bug fixes
- Allow only
Numericvalues to be the “actual” in thebe_withinmatcher. This prevents confusing error messages. (Su Zhang @zhangsu) - Define
shouldandshould_notonBasicObjectrather thanKernelon 1.9. This makesshouldandshould_notwork properly withBasicObject-subclassed proxy objects likeDelegator. (Myron Marston)
rspec-mocks-2.11.0
Enhancements
- expose ArgumentListMatcher as a formal API
- supports use by 3rd party mock frameworks like Surrogate
- Add
stub_constAPI to stub constants for the duration of an example (Myron Marston).
Bug fixes
- Fix regression of edge case behavior.
double.should_receive(:foo) { a }was causing a NoMethodError whendouble.stub(:foo).and_return(a, b)had been setup before (Myron Marston). - Infinite loop generated by using
any_instanceanddup. (Sidu Ponnappa @kaiwren) double.should_receive(:foo).at_least(:once).and_return(a)always returns a even if:foois already stubbed.- Prevent infinite loop when interpolating a null double into a string
as an integer (
"%i" % double.as_null_object). (Myron Marston) - Fix
should_receiveso that null object behavior (e.g. returning self) is preserved if no implementation is given (Myron Marston). - Fix
and_raiseso that it raisesRuntimeErrorrather thanExceptionby default, just like ruby does. (Andrew Marshall)
rspec-rails-2.11.0
Enhancements
- The generated
spec/spec_helper.rbsetsconfig.order = "random"so that specs run in random order by default. - rename
render_templatetohave_rendered(and alias torender_templatefor backward compatibility)
Bug fixes
- “uninitialized constant” errors are avoided when using using gems like
rspec-rails-uncommittedthat defineRspec::Railsbeforerspec-railsloads (Andy Lindeman)
rspec-expectations-2.9.1 is released!
April 3rd, 2012
This is a bug-fix only release, and is recommended for everybody using rspec-2.9.
Bug fixes
- Provide a helpful message if the diff between two objects is empty.
- Fix bug diffing single strings with multiline strings.
- Fix for error with using custom matchers inside other custom matchers (mirasrael)
- Fix using execution context methods in nested DSL matchers (mirasrael)
rspec-rails-2.8.1 is released
January 5th, 2012
Bug fix release
The rails-3.2.0.rc2 release broke stub_model in rspec-rails-2.0.0 > 2.8.0.
The rspec-rails-2.8.1 release fixes this issue, but it means that when you
upgrade to rails-3.2.0.rc2 or greater, you’ll have to upgrade to
rspec-rails-2.8.1 or greater.
Because rspec-rails-2.8.1 supports all versions of rails since 3.0, I recommend that you upgrade to rspec-rails-2.8.1 first, and then upgrade to rails-3.2.0.rc2 (or 3.2.0 once it’s out).
Changelog
http://rubydoc.info/gems/rspec-rails/file/Changelog.md
Docs
http://rubydoc.info/gems/rspec-rails
http://relishapp.com/rspec/rspec-rails
RSpec-2.8 is released!
January 4th, 2012
We released RSpec-2.8.0 today with a host of new features and improvements since 2.7. Some of the highlights are described below, but you can see the full changelogs at:
- http://rubydoc.info/gems/rspec-core/file/Changelog.md
- http://rubydoc.info/gems/rspec-expectations/file/Changelog.md
- http://rubydoc.info/gems/rspec-mocks/file/Changelog.md
- http://rubydoc.info/gems/rspec-rails/file/Changelog.md
Documentation
While not 100% complete yet, we’ve made great strides on RSpec’s RDoc:
- http://rubydoc.info/gems/rspec-core
- http://rubydoc.info/gems/rspec-expectations
- http://rubydoc.info/gems/rspec-mocks
- http://rubydoc.info/gems/rspec-rails
http://rspec.info is now just a one pager (desperate for some design love - volunteers please email rspec-users@rubyforge.org). All the old pages are redirects to the relevant RDoc at http://rubydoc.info. RSpec-1 info is still available at http://old.rspec.info.
We’ve still got Cucumber features up at http://relishapp.com/rspec, but we’re going to be phasing that out as the primary source of documentation. There are a lot of reasons for this, and I’ll try to follow up with a separate blog post on this topic.
rspec-core
Improved support for tags and filtering
You can now set default tags/filters in either RSpec.configure or a .rspec
file and override these tags on the command line. For example, this configuration
tells rspec to run all the examples that are not tagged :slow:
# in spec/spec_helper.rb RSpec.configure do |c| c.treat_symbols_as_metadata_keys_with_true_values = true c.filter_run_excluding :slow end
Now when you want run those, you can just do this:
rspec --tag slow
This will override the configuration and run onlly the examples tagged :slow.
–order rand
We added an --order option with two supported values: rand and default.
rspec --order random (or rand) tells RSpec to run the groups in a random
order, and then run the examples within each group in random order. We
implemented it this way (rather than complete randomization of every example)
because we don’t want to re-run expensive before(:all) hooks. A fair tradeoff,
as the resulting randomization is just as effective at exposing
order-dependency bugs.
When you use --order random, RSpec prints out the random number it used to
seed the randomizer. When you think you’ve found an order-dependency bug, you
can pass the seed along and the order will remain consistent:
--order rand:3455
--order default tells RSpec to load groups and examples as they are declared
in each file.
rspec –init
We added an --init switch to the rspec command to generate a “spec”
directory, and “.rspec” and “spec/spec_helper.rb” files with some starter code
in them.
rspec-expectations
We discovered that the matcher DSL generates matchers that run considerably slower than classes which implement the matcher protocol. We made some minor improvements in the DSL, but to really improve things we re-implemented every single built-in matcher as a class.
rspec-2.8.0.rc1 is released
November 6th, 2011
I just released rspec-2.8.0.rc1, which includes releases of rspec-core, rspec-expectations, rspec-mocks, and rspec-rails. Changelogs for each are at:
What’s new
Nothing really changed in rspec-rails or rspec-mocks, but rspec-core and rspec-expectations have both gotten some nice improvements.
Configuration (rspec-core)
rspec-core offers a number of configuration options which can be declared on
the command line, in a config file (.rspec, ~/.rspec, or custom location),
as well as in an RSpec.configure block (in spec/spec_helper.rb by
convention). Before this release, some options, but not all, could be stored in
config files and then overridden on the command line. The problems were that it
was inconsistent (not all options worked this way), and we couldn’t override
options that were set in RSpec.configure blocks.
With this release, almost all options declared in RSpec.configure can be
overridden from the command line, and --tag options can override their
inverses. For example, if you have this in .rspec:
--tag ~slow:true
That means “exclude examples tagged :slow => true“. So the following example
would be excluded:
it "does something", :slow => true do # ... end
You can also exclude that example from RSpec.configure like this:
RSpec.configure do |c| c.filter_run_excluding :slow => true end
Note: the naming is different for historical reasons, and we will reconcile
that in a future release, but for now, just know that --tag on the command
line and in .rspec is synonymous with filter_run_[including|excluding] in
RSpec.configure.
Override from command line
Whether the default is stored in .rspec or RSpec.configure, it can be overridden
from the command line like this:
rspec --tag slow:true
“Profiles” in custom options files
The rspec command has an --options option that let’s store command line args in
arbitrary files and tell RSpec where to find them. For example, you could set things
up so your normal spec run excludes the groups and examples marked :slow by putting
this in .rspec:
--tag ~slow
Now add a .slow file with:
--tag slow
Now run rspec to run everything but the slow specs, and run rspec --options
.slow or rspec -O.slow to run the slow ones.
Override from Rake task
RSpec’s Rake task supports an rspec_opts config option, which means you can
set up different groupings from rake tasks as well. The fast/slow example above
would look like this:
namespace :spec do desc "runs the fast specs" RSpec::Core::RakeTask.new(:fast) do |t| t.rspec_opts = '--options .fast' end RSpec::Core::RakeTask.new(:slow) do |t| t.rspec_opts = '--options .slow' end end
Or ..
namespace :spec do desc "runs the fast specs" RSpec::Core::RakeTask.new(:fast) do |t| t.rspec_opts = '--tag ~slow' end RSpec::Core::RakeTask.new(:slow) do |t| t.rspec_opts = '--tag slow' end end
Implicit true value for tags/filters
This is not new in rspec-2.8, but all the tags/filters in the example above can
be written without explicitly typing true:
--tag slow --tag ~slow
RSpec.configure {|c| c.filter_run_excluding :slow} it "does something", :slow do
You have to set a config option to enable this in rspec-2.x:
RSpec.configure {|c| c.treat_symbols_as_metadata_keys_with_true_values = true}
In rspec-3.0, this will be the default, but without setting this value in 2.x you’ll get a deprecation warning when you try to configure things this way. It’s ugly, I know, but this enabled us to introduce the new behavior without breaking compatibility with some suites in a minor release.
Ordering
With 2.8, you can now run the examples in random order, using the new --order
option:
--order rand
The order is randomized with some reasonable caveats:
- top level example groups are randomized
- nested groups are randomized within their parent group
- examples are randomized within their group
This provides a very useful level of randomization while maintaining the
integrity of before/after hooks, subject, let, etc.
If you want to run the examples in the default ordering (file-system load order for files and declaration order for groups/examples), you can override the order from the command line:
--order default
Pseudo-randomization
The randomization is managed by Ruby’s pseudo-randomization. This means that if you find an order dependency and want to debug/fix it, you can fix the order by providing the same seed for each run:
--order rand:1234
The seed is printed to the console with each run, so you can just copy it to the
command. You can also just specify the seed, which RSpec will assume means you want
to run with --order rand:
--seed 1234
Every time you run the suite with the same seed, the examples will run in the same “random” order.
Built-in matchers are all classes in rspec-expectations
The Matcher DSL in rspec-expectations makes it dead simple to define custom matchers that suit your domain. The problem is that it is several times slower than defining a class to do so. While this doesn’t make much difference when you have a custom matcher that you use a few dozen times (where talking hundredths of seconds here), it does make a difference if every single matcher invocation in your entire suite suffers this problem.
The short term fix is that all of the built-in matchers have been re-implemented as classes rather than using the DSL to declare them. This has the added benefit of making it easier to navigate the code and RDoc
Longer term, we’ll try to refactor the internals of the matcher DSL so that it generates a class at declaration time. Eventually.
Summing up
So that’s it. Nothing ground breaking. Nothing compatibility breaking. But some nice new features and improvements that will make your life just a little bit nicer when you upgrade. We’re doing a release candidate because enough changed internally that I want to give you time to try it out, so please, please do so! And please report any issues you’re having with this upgrade to:
Assuming there are no significant issues, I’ll release 2.8 final within a week or two.
Happy spec’ing!
David
rspec-core 2.7.1 is released!
October 20th, 2011
rspec-core-2.7.1
- Bug fixes
- tell autotest the correct place to find the rspec executable
rspec-2.7.0 is released!
October 16th, 2011
We’re pleased to announce the release of rspec-2.7.0. Release notes for each gem are listed below, but here are a couple of highlights:
Just type rspec
With the the 2.7.0 release, if you keep all of your specs in the conventional
spec directory, you don’t need to follow the rspec command with a path.
Just type rspec.
If you keep your specs in a different directory, just set the --default_path
option to that directory on the command line, or in a .rspec config file.
The rake task now lets Bundler manage Bundler
The RSpec::Core::RakeTask invokes the rspec command in a subshell. In
recent releases, it assumed that you wanted it prefixed with bundle exec if
it saw a Gemfile. We then added gemfile and skip_bundler options to the
task, so you could manage this in different ways.
It turns out that Bundler manages this quite well without any help from RSpec.
If you activate Bundler in the parent shell, via the command line or
Bundler.setup, it sets environment variables that activate Bundler in the
subshell with the correct gemfile.
The gemfile and skip_bundler options are therefore deprecated and have no
effect.
Release Notes
rspec-core-2.7.0
NOTE: RSpec’s release policy dictates that there should not be any backward incompatible changes in minor releases, but we’re making an exception to release a change to how RSpec interacts with other command line tools.
As of 2.7.0, you must explicity require "rspec/autorun" unless you use the
rspec command (which already does this for you).
Enhancements
- Add example.exception (David Chelimsky)
--default_pathcommand line option (Justin Ko)- support multiple
--line_numberoptions (David J. Hamilton)- also supports
path/to/file.rb:5:9(runs examples on lines 5 and 9)
- also supports
- Allow classes/modules to be used as shared example group identifiers (Arthur Gunn)
- Friendly error message when shared context cannot be found (Sławosz Sławiński)
- Clear formatters when resetting config (John Bintz)
- Add
xspecifyand xexample as temp-pending methods (David Chelimsky) - Add
--no-drboption (Iain Hecker) - Provide more accurate run time by registering start time before code is loaded (David Chelimsky)
- Rake task default pattern finds specs in symlinked dirs (Kelly Felkins)
- Rake task no longer does anything to invoke bundler since Bundler already handles it for us. Thanks to Andre Arko for the tip.
- Add
--failure-exit-codeoption (Chris Griego)
Bug fixes
- Include
Rake::DSLto remove deprecation warnings in Rake > 0.8.7 (Pivotal Casebook) - Only eval
letblock once even if it returnsnil(Adam Meehan) - Fix
--patternoption (wasn’t being recognized) (David Chelimsky) - Only implicitly
require "rspec/autorun"with therspeccommand (David Chelimsky) - Ensure that rspec’s
at_exitdefines the exit code (Daniel Doubrovkine) - Show the correct snippet in the HTML and TextMate formatters (Brian Faherty)
- Include
rspec-expectations-2.7.0
Enhancements
- HaveMatcher converts argument using
to_i(Alex Bepple & Pat Maddox) - Improved failure message for the
have_xxxmatcher (Myron Marston) - HaveMatcher supports
count(Matthew Bellantoni) - Change matcher dups
Enumerablebefore the action, supporting customEnumerabletypes likeCollectionProxyin Rails (David Chelimsky)
- HaveMatcher converts argument using
Bug fixes
- Fix typo in
have(n).xyzdocumentation (Jean Boussier) - fix
safe_sortfor ruby 1.9.2 (Kernelnow defines<=>for Object) (Peter van Hardenberg)
- Fix typo in
rspec-mocks-2.7.0
Enhancements
- Use
__send__rather thansend(alextk) - Add support for
any_instance.stub_chain(Sidu Ponnappa) - Add support for
any_instanceargument matching based onwith(Sidu Ponnappa and Andy Lindeman)
- Use
Changes
- Check for
failure_message_for_shouldorfailure_messageinstead ofdescriptionto detect a matcher (Tibor Claassen)
- Check for
Bug fixes
- pass a hash to
any_instance.stub. (Justin Ko) - allow
to_aryto be called without raisingNoMethodError(Mikhail Dieterle) any_instanceproperly restores private methods (Sidu Ponnappa)
- pass a hash to
rspec-rails-2.7.0
Enhancments
ActiveRecord::Relationcan use the=~matcher (Andy Lindeman)- Make generated controller spec more consistent with regard to ids (Brent J. Nordquist)
- Less restrictive autotest mapping between spec and implementation files (José Valim)
require 'rspec/autorun'from generatedspec_helper.rb(David Chelimsky)- add
bypass_rescue(Lenny Marks) route_toaccepts query string (Marc Weil)
Internal
- Added specs for generators using ammeter (Alex Rothenberg)
Bug fixes
- Fix configuration/integration bug with rails 3.0 (fixed in 3.1) in which
fixure_file_uploadreads fromActiveSupport::TestCase.fixture_pathand misses RSpec’s configuration (David Chelimsky) - Support nested resource in view spec generator (David Chelimsky)
- Define
primary_keyon class generated bymock_model("WithAString")(David Chelimsky)
- Fix configuration/integration bug with rails 3.0 (fixed in 3.1) in which
Avoid stubbing methods invoked by a framework
September 22nd, 2011
In a github issue reported to the rspec-mocks project, the user had run into a problem in a Rails’ controller spec in which an RSpec-generated test double didn’t behave as expected. What follows is an edited version of the issue and my response, with the hope that it reaches a wider audience and/or sparks some conversation.
The reported problem: ActiveSupport::JSON::Encoding::CircularReferenceError using doubles
This spec …
require 'spec_helper' describe ListsController do let(:list) { double("List") } describe "GET 'index'" do let(:expected) { [{id: "1", name: "test"}] } before do list.stub(:id){ "1" } list.stub(:name){ "test" } List.stub(:select){ [ list ] } end it "should return the list of lists" do get :index, format: :json response.body.should == expected.to_json end end end
… plus this implementation …
class ListsController < ApplicationController respond_to :json expose(:lists) { List.select("id, name") } def index respond_with(lists) end end
… produces this failure:
Failure/Error: get :index, format: :json
ActiveSupport::JSON::Encoding::CircularReferenceError:
object references itselfThe deeper problem: this is a great example of when not to use stubs.
Here’s why: there are three incorrect assumptions hiding behind the stubs!
selecttakes an Array:List.select(["id","name"]), but the example stubs it incorrectly.- the id is numeric, but the example uses String.
- the json is wrapped:
{"list":{"id":1,"name":"test"}}, but the example doesn’t wrap it.
Even if the stubs were properly aligned with reality, the reason for the error
is that respond_with(lists) eventually calls as_json on the list object,
which, in this example, is an RSpec double that doesn’t implement as_json.
We need to either use a stub_model (which does implement as_json), or
explicitly stub it in the example:
list.stub(:as_json) { { list: {id: 1, name: "test"} } }
But I’d avoid stubs altogether in this case. Stubs are great for well defined
(and understood) public APIs which are invoked by the code being specified.
In this case, we’re stubbing an API (as_json) that is invoked by the Rails
framework, not the code being specified. If the Rails framework ever changes
how it renders json, this example would continue to pass, but it would be a
false positive.
One possible remedy
Here’s how I’d approach this outside-in (based on my own flow, design preferences, and target outcomes. YMMV.)
Start with a request spec:
require 'spec_helper' describe "Lists" do describe "GET 'index.json'" do it "returns the list of lists" do list = List.create!(name: "test") get "/lists.json" response.body.should == [{list: {id: list.id, name: "test"}}].to_json end end end
This shows exactly what to expect, so when working on clients we can refer directly to this without having to dig into internals.
Run this and it fails with uninitialized constant List, so generate the list
resource:
rails generate resource list name:string rake db:migrate rake db:test:prepare
Run it again and it fails with ActionView::MissingTemplate. Now we have a
couple of choices. The purist view says “write a controller spec”, but some
people say controller specs are unnecessary if there are already request specs
(or cukes) as they just add duplication.
For me, the answer depends upon the complexity of the requirement as it
compares to what we get for free from Rails. In this case, the only difference
between the requirement and what Rails gives us for free is that we constrain
the fields to id and name This is something we can implement in the model,
so I’d just implement this very simple controller code and move on:
class ListsController < ApplicationController respond_to :json def index respond_with List.all end end
Now the request spec fails with:
expected: "[{\"list\":{\"id\":1,\"name\":\"test\"}}]"
got: "[{\"list\":{\"created_at\":\"2011-08-27T14:56:19Z\",\"id\":1,\"name\":\"test\",\"updated_at\":\"2011-08-27T14:56:19Z\"}}]"We’re getting more key/value pairs than we want. I want the model responsible for constraining the keys in the json (Rails implements json transformations in the context of the model, so why shouldn’t we?), so I’d add a model spec:
require 'spec_helper' describe List do describe "#as_json" do it "constrains keys to id and name" do list = List.new(:name => "things") list.as_json['list'].keys.should eq(%w[id name]) end end end
This fails with:
expected ["id", "name"]
got ["created_at", "name", "updated_at"]I expect to see created_at and updated_at, but I’m surprised (initially) to
see that id is missing. Thinking this through, it makes sense because the
example generates the list using new, so no id is generated. To get id
to show up in the list of keys, we can use create instead of new, or we can
explicitly set it. I’m going to go with setting the id explicitly to avoid the
db hit, accepting the self-imposed leaky abstraction. It’s all trade-offs.
it "constrains fields to id and name" do list = List.new(:name => "things") list.id = 37 list.as_json['list'].keys.should eq(%w[id name]) end
Now it fails with:
expected ["id", "name"]
got ["created_at", "id", "name", "updated_at"]Now we can implement the constraint:
class List < ActiveRecord::Base def as_json super({ only: %w[id name]}) end end
Now the model spec passes, but the request spec fails with:
ArgumentError: wrong number of arguments (1 for 0)
This is because the as_json implementation fails to honor the Rails
API:
as_json(options = nil)
as_json is called by the Rails framework with an options hash. Had we done
this without the request spec and weren’t aware of this information, we’d have
a bunch of passing specs but the app would blow up. Hooray for testing at
multiple levels!
So we add a new example to the model spec:
it "honors the submitted options hash" do list = List.new(:name => "things") list.id = 37 list.as_json(:only => :name)['list'].keys.should eq(%w[name]) end
This fails with wrong number of arguments (1 for 0) as well, so now we adjust
the model implementation:
def as_json(opts={}) super({ only: %w[id name]}.merge(opts)) end
Now the model spec passes again, and so does the request spec! DONE!
The result is a very nice balance of clarity, speed (in spite of the one db hit in the request spec) and flexibility. Any new endpoints we add will get the same json representation because it is expressed in the model (heeding the principle of least surprise). The model spec not only specifies how the model should represent itself as json, but it helps to explain how the rails framework uses the model. All of this with no stubbing at all, and especially no stubbing of APIs our code isn’t invoking.

