How can you set up an rspec so that the render is called only once for all of its specs?
I have the following view specification:
describe "carts/show" do
before(:each) do
book = build_stubbed(:product, title: "book")
car = build_stubbed(:product, title: "car")
line_item1 = build_stubbed(:line_item, product: book, quantity: 3)
line_item2 = build_stubbed(:line_item, product: car, quantity: 1)
@cart = assign(:cart, build_stubbed(:cart, line_items: [line_item1, line_item2]))
render
end
it "displays a single car" do
assert_select "li", text: "1 time: car"
end
it "displays 3 books" do
assert_select "li", text: "3 times: book"
end
it "has exactly 2 items" do
assert_select "li", 2
end
end
These specifications work as expected. However, because the setup is in a block before(:each)
, the setup method is render
called once per block. It's pretty slow. Ideally, they only need to be called once for the entire block describe
.
I thought changing before(:each)
to would before(:all)
fix this, but I got the error (line 16 in show.html.erb_spec.rb - call render
):
NoMethodError: undefined method `example_group' for nil:NilClass
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-rails-2.12.0/lib/rspec/rails/example/view_example_group.rb:106:in `_default_file_to_render'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-rails-2.12.0/lib/rspec/rails/example/view_example_group.rb:112:in `_default_render_options'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-rails-2.12.0/lib/rspec/rails/example/view_example_group.rb:45:in `render'
show.html.erb_spec.rb:16:in `block (2 levels) in <top (required)>'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/hooks.rb:23:in `instance_eval'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/hooks.rb:23:in `run'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/hooks.rb:106:in `run'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/hooks.rb:424:in `run_hook'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/example_group.rb:319:in `run_before_all_hooks'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/example_group.rb:368:in `run'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/command_line.rb:28:in `block (2 levels) in run'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/command_line.rb:28:in `map'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/command_line.rb:28:in `block in run'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/reporter.rb:34:in `report'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/command_line.rb:25:in `run'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/runner.rb:80:in `run'
~/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.12.1/lib/rspec/core/runner.rb:17:in `block in autorun'
I don't know what else I could do here. What is the correct way to solve this problem?
source to share
RSpec-Rails rendering definitely assumes that it occurs in every test, which will make it a little awkward and we need to play with the internal state from its appearance.
Due to the shaking around rspec-rails, it looks like it is delegating its call render
ActionView::TestCase::Behavior#render
. @rendered
seems to be a state that matters (and probably @view
does), but I have no idea if this will lead to more errors.
Here's my totally untested approach:
describe "carts/show" do
# Memoize the view across test runs for speed; this is some dangerous
# spelunking in internal state, though!
rendered_view = nil
rendered_content = nil
before(:each) do
if rendered_view && rendered_content
@view = rendered_view
@rendered = rendered_content
else
book = build_stubbed(:product, title: "book")
car = build_stubbed(:product, title: "car")
line_item1 = build_stubbed(:line_item, product: book, quantity: 3)
line_item2 = build_stubbed(:line_item, product: car, quantity: 1)
@cart = assign(:cart, build_stubbed(:cart, line_items: [line_item1, line_item2]))
render
rendered_view = @view
rendered_content = @rendered
end
end
it "displays a single car" do
assert_select "li", text: "1 time: car"
end
it "displays 3 books" do
assert_select "li", text: "3 times: book"
end
it "has exactly 2 items" do
assert_select "li", 2
end
end
source to share
It's not perfect, but you can use shared_subject
To use it, you essentially:
- Replace "subject" with "shared_subject" to share the first acrooss result of the entire context.
- (optional) replace 'before (: each)' blocks with 'shared_setup'
- Done. The subject will be passed into one, single context (the nested ones will be re-configured again).
So your example would look like this:
describe "carts/show" do
shared_subject do
book = build_stubbed(:product, title: "book")
car = build_stubbed(:product, title: "car")
line_item1 = build_stubbed(:line_item, product: book, quantity: 3)
line_item2 = build_stubbed(:line_item, product: car, quantity: 1)
@cart = assign(:cart, build_stubbed(:cart, line_items: [line_item1, line_item2]))
render
end
it "displays a single car" do
assert_select "li", text: "1 time: car"
end
it "displays 3 books" do
assert_select "li", text: "3 times: book"
end
it "has exactly 2 items" do
assert_select "li", 2
end
end
source to share