John Feminella
2011-Jun-24 12:17 UTC
[rspec-users] Is there a way to run `let` once per context?
hello, I have a monolithic test that looks like this: =====describe Model do describe "#method" do let!(:expensive) { ... } it "should do a lot of things" do expensive.should be_foo expensive.should be_bar expensive.should be_baz end end end ===== I would like to refactor the large spec into smaller ones: ===== it "should be foo" { expensive.should be_foo } it "should be bar" { expensive.should be_bar } it "should be baz" { expensive.should be_baz } ===== However, doing this slows down the tests a bit, because the `expensive` method requires hitting the database. Suppose that I can guarantee that none of the tests or the methods they call will be modifying the object returned by the `expensive` method. Is there a way to tell RSpec to memoize the `expensive` result across the context, and not just across an individual test run? Alternatively, should I just use before(:all) for this? -- John Feminella Principal Consultant, BitsBuilder LI: http://www.linkedin.com/in/johnxf SO: http://stackoverflow.com/users/75170/
David Chelimsky
2011-Jun-24 12:48 UTC
[rspec-users] Is there a way to run `let` once per context?
On Jun 24, 2011, at 7:17 AM, John Feminella wrote:> hello, > > I have a monolithic test that looks like this: > > =====> describe Model do > describe "#method" do > let!(:expensive) { ... } > > it "should do a lot of things" do > expensive.should be_foo > expensive.should be_bar > expensive.should be_baz > end > end > end > =====> > I would like to refactor the large spec into smaller ones: > > =====> it "should be foo" { expensive.should be_foo } > it "should be bar" { expensive.should be_bar } > it "should be baz" { expensive.should be_baz } > =====> > However, doing this slows down the tests a bit, because the > `expensive` method requires hitting the database. > > Suppose that I can guarantee that none of the tests or the methods > they call will be modifying the object returned by the `expensive` > method. Is there a way to tell RSpec to memoize the `expensive` result > across the context, and not just across an individual test run? > > Alternatively, should I just use before(:all) for this?Just use before(:all). That''s what it is designed for, and it tells you that you have stuff to clean up in an after(:all) hook. HTH, David
John Feminella
2011-Jun-24 13:08 UTC
[rspec-users] Is there a way to run `let` once per context?
>> Alternatively, should I just use before(:all) for this? > > Just use before(:all). That''s what it is designed for, and it tells you that you have stuff to clean up in an after(:all) hook.I was about to take your advice, when I discovered another problem: =====RSpec.configure do |config| config.mock_with :rspec config.before(:each, :geocoding => nil) do Geocoding.stub(:client).and_return(Geocoding::StubGeoClient.new) end end ===== Since the test-specific before-all hook runs first before the global before-each hook, that means that the `expensive` operation now hits an external geocoding service, which makes it take even longer. It also makes tests fail because WebMock is running, and it prohibits network connections you don''t explicitly specify. The fact that this is becoming obnoxious suggests that something else is probably bad (e.g. maybe the `expensive` model should be changed to be less expensive to create). I think I''ll do that instead of trying to go down this road further. Thanks for your help, David. ~ jf On Fri, Jun 24, 2011 at 08:48, David Chelimsky <dchelimsky at gmail.com> wrote:> > On Jun 24, 2011, at 7:17 AM, John Feminella wrote: > >> hello, >> >> I have a monolithic test that looks like this: >> >> =====>> describe Model do >> ?describe "#method" do >> ? ?let!(:expensive) { ... } >> >> ? ?it "should do a lot of things" do >> ? ? ?expensive.should be_foo >> ? ? ?expensive.should be_bar >> ? ? ?expensive.should be_baz >> ? ?end >> ?end >> end >> =====>> >> I would like to refactor the large spec into smaller ones: >> >> =====>> ? ?it "should be foo" { expensive.should be_foo } >> ? ?it "should be bar" { expensive.should be_bar } >> ? ?it "should be baz" { expensive.should be_baz } >> =====>> >> However, doing this slows down the tests a bit, because the >> `expensive` method requires hitting the database. >> >> Suppose that I can guarantee that none of the tests or the methods >> they call will be modifying the object returned by the `expensive` >> method. Is there a way to tell RSpec to memoize the `expensive` result >> across the context, and not just across an individual test run? >> >> Alternatively, should I just use before(:all) for this? > > Just use before(:all). That''s what it is designed for, and it tells you that you have stuff to clean up in an after(:all) hook.
David Chelimsky
2011-Jun-24 13:15 UTC
[rspec-users] Is there a way to run `let` once per context?
On Jun 24, 2011, at 8:08 AM, John Feminella wrote:>>> Alternatively, should I just use before(:all) for this? >> >> Just use before(:all). That''s what it is designed for, and it tells you that you have stuff to clean up in an after(:all) hook. > > I was about to take your advice, when I discovered another problem: > > =====> RSpec.configure do |config| > config.mock_with :rspec > > config.before(:each, :geocoding => nil) do > Geocoding.stub(:client).and_return(Geocoding::StubGeoClient.new) > end > end > =====> > Since the test-specific before-all hook runs first before the global > before-each hook, that means that the `expensive` operation now hits > an external geocoding service, which makes it take even longer. It > also makes tests fail because WebMock is running, and it prohibits > network connections you don''t explicitly specify.You can resolve this using a shared group: http://relishapp.com/rspec/rspec-core/v/2-6/dir/example-groups/shared-examples http://relishapp.com/rspec/rspec-core/v/2-6/dir/example-groups/shared-context> > The fact that this is becoming obnoxious suggests that something else > is probably bad (e.g. maybe the `expensive` model should be changed to > be less expensive to create).That''s definitely worth investigating :)> I think I''ll do that instead of trying > to go down this road further. Thanks for your help, David.y/w Cheers, David> > ~ jf > > On Fri, Jun 24, 2011 at 08:48, David Chelimsky <dchelimsky at gmail.com> wrote: >> >> On Jun 24, 2011, at 7:17 AM, John Feminella wrote: >> >>> hello, >>> >>> I have a monolithic test that looks like this: >>> >>> =====>>> describe Model do >>> describe "#method" do >>> let!(:expensive) { ... } >>> >>> it "should do a lot of things" do >>> expensive.should be_foo >>> expensive.should be_bar >>> expensive.should be_baz >>> end >>> end >>> end >>> =====>>> >>> I would like to refactor the large spec into smaller ones: >>> >>> =====>>> it "should be foo" { expensive.should be_foo } >>> it "should be bar" { expensive.should be_bar } >>> it "should be baz" { expensive.should be_baz } >>> =====>>> >>> However, doing this slows down the tests a bit, because the >>> `expensive` method requires hitting the database. >>> >>> Suppose that I can guarantee that none of the tests or the methods >>> they call will be modifying the object returned by the `expensive` >>> method. Is there a way to tell RSpec to memoize the `expensive` result >>> across the context, and not just across an individual test run? >>> >>> Alternatively, should I just use before(:all) for this? >> >> Just use before(:all). That''s what it is designed for, and it tells you that you have stuff to clean up in an after(:all) hook. > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-usersCheers, David