Maybe it is just me, but I had suspected some weirdness here and there from using the fancy “let(:var_sym)” syntax. The trusty RSpec book says:
The first call to let( ) defines a memoized output( ) method that returns
a double object. Memoized means that the first time the method is
invoked, the return value is cached and that same value is returned
every subsequent time the method is invoked within the same scope.
So, it would seem that let() is a great way to define an object once, and use it from there onward.
However, I saw that in this particular instance of running an “expensive” operation in the let block, it took 17 seconds instead of 7 seconds to run the specs! I could see my specs ticking along, very slowly, one at a time. What the heck? I asked myself. Is there something that says “turn caching off (or on)?”
Fancy Schmancy! To save ~10 seconds, I’ll forgo the niceties of let() and revert to using the @var_name syntax.
Given the following RSpec code:
context 'instance methods' do
let(:sample_xml_file) {File.expand_path('../../data/sample_v_1_13.xml', __FILE__)}
let(:p) {
xml_str = File.read(sample_xml_file)
Nemsis::Parser.new(xml_str)
}
let(:r) {Nemsis::Renderer::HTML.new(p)}
describe '#render_html' do
context "plain HTML" do
let(:html) { r.render(false) }
it 'returns not nil' do
html.should_not be_nil
end
it 'has title section' do
html.should =~ ...
end
context 'specialty patient section' do
it('has specialty patient') { html.should =~ ... }
it('has specialty patient trauma criteria') { html.should =~ ... }
it('has specialty patient airway') { html.should =~ ... }
end
it "should not have a STYLE section" do
html.should_not =~ ...
end
it "write to html file" do
write_html_file(sample_xml_file, "simple", html)
end
end
context "fancy HTML" do
let(:html) { r.render(true) }
it "should have a STYLE section" do
html.should =~ ...
end
it "write to html file" do
write_html_file(sample_xml_file, "fancy", html)
end
end
end
...
Contrast the above with the more traditional approach that uses a before block and @variables:
context 'instance methods' do
before :all do
@sample_xml_file = File.expand_path('../../data/sample_v_1_13.xml', __FILE__)
xml_str = File.read(@sample_xml_file)
p = Nemsis::Parser.new(xml_str)
r = Nemsis::Renderer::HTML.new(p)
@html = r.render(false)
end
describe '#render_html' do
context "plain HTML" do
it 'returns not nil' do
@html.should_not be_nil
end
I did a bit more formal timing, which revealed the truth:
- let() — 10.7 seconds
- before block — 2.4 seconds
Am I missing something?

Yes, you are missing something. let() memoizes per example, whereas before(:all) runs once per group. I’ll beef up the rdoc to clarify this.
Yea, I guess when I saw
let()at the top of the Codebreaker example, that the items would be defined once, and then used in each of the two ensuing “it should” examples.So is this a good rule of thumb: “if the let() block is computationally expensive, use a before block instead?”
Pingback: 当使用RSpec let()? – CodingBlog
Pingback: When to use RSpec let()? - ExceptionsHub
Pingback: [ruby] RSpec let ()을 언제 사용합니까? - 리뷰나라