-
-
Save erik-megarad/5df43818f1058f380631 to your computer and use it in GitHub Desktop.
describe "A long-running process" do | |
before(:context) do # Have to use before(:context) instead of subject() to make the long-running task only execute once | |
@input_one = 1 | |
@input_two = 2 # Can't use let() in before(:context) | |
@my_test_object = MyTestClass.new | |
@my_test_object.thing_that_takes_thirty_minutes(@input_one, @input_two) | |
end | |
# Multiple assertions | |
it 'calculates the output correctly' do | |
@my_test_object.value.should eq 3 | |
end | |
it 'does not set the failed flag' do | |
@my_test_object.failed.should be_nil | |
end | |
end |
Here's a little extension that provides something very similar to what you're asking for:
module RSpecContextMemoization
def let(name, &block)
attr_reader name
before(:context) { instance_variable_set(:"@#{name}", block.call) }
end
end
RSpec.configure do |config|
config.extend RSpecContextMemoization, memoization_scope: :context
end
Put that in spec/spec_helper.rb
(or somewhere that is always loaded) and then tag example groups in which you want let
/subject
memoization to have context rather than example scope with memoization_scope: :context
. Your example would become:
describe "A long-running process", memoization_scope: :context do
let(:input_one) { 1 }
let(:input_two) { 2 }
let(:my_test_object) { MyTestClass.new }
subject(:thing_that_takes_thirty_minutes) do
my_test_object.thing_that_takes_thirty_minutes(input_one, input_two)
end
it 'calculates the output correctly' do
expect { thing_that_takes_thirty_minutes }.to change {
my_test_object.value
}.to(3)
end
it 'does not set the failed flag' do
expect { thing_that_takes_thirty_minutes }.to_not change {
my_test_object.failed
}
end
end
...and it should just work.
It's trivial to add this functionality on top of what RSpec already provides. We field enough questions where users get confused when misusing before(:context)
hooks that I don't want to add more features to RSpec that would encourage their (mis)use, particularly because it's so easy to add this kind of thing on top of the APIs already provided. It could make a great extension gem, though.
Thanks for the response. I wasn't referring to betterspecs.org, specifically, but the general feeling that using instance variables instead of
let
seems like a dirty hack. You also lose some convenient things like memoization, which aren't particularly important in this situation.I was indeed attempting to follow the "one expectation/assertion per example/test" guideline, mostly because if multiple assertions fail, I want to see each one that failed, instead of just the first. If I want that, I'm forced to use
before(:context)
, which also seems like a dirty hack.Writing tests this way means you leave behind most of the benefits of rspec, in my opinion. Things still "work," but it's no longer pleasant to write. In an ideal world, this would be how this test would look:
Note how it looks the same as any other test, with the only addition being a flag on
subject
(calledcontext
here, but there's probably a better name for it) that instructs rspec to only evaluate thesubject
once percontext
. That seems like an elegant solution for everybody.