I’m not opposed to the careful use of before(:all), and I certainly understand the compromises we have to make to keep our test suites running quickly. And you obviously understand that too.
My answer to the build issue has been mocking and stubbing, which I know can clutter the tests at times, and make it more difficult for junior developers to understand/maintain. I think in a perfect world, you wouldn’t deviate from before(:each) and I wouldn’t fake any of my models or associations :) The common culprit is test speed, and so far there isn’t a perfect answer.
I’m to the point where I want to start exploring the idea of serious multicore servers (onsite) dedicated to my company’s testing suites and used by our developers. Our rails shop is growing, so it might even be cost effective – or at least justifiable. Plus, I’ve always wanted a good excuse to own that kind of hardware!
Oh, there’s a gem called dataset you might like. It has the speed of fixtures because it loads the database the same way before tests, but it allows you to have *multiple* sets. So you can have the simplest set of records for a collection of tests, making it easy to remember what everything does. I don’t know if it’s been maintained, I’ll have to check because I could use it myself. And Postgres’ transactions might help, too. So many options, so little time.
Thanks for responding to my comments, it’s great talking to others with this level of dedication to the craft!
]]>pre
would have done it. I updated your comment. It looks gorgeous now. :)
]]>I’m not a big fan of using: subject.should ...
. I prefere account.should ...
by far even if I have to add a let statement. :)
I didn’t have much success with Factory.build
. You can’t really use Factory.build
whenever your model depends on others (an account must have a user, a balance and an account_email) or you use gems that saves object for you (state_machine does) or you use callbacks (disabling an account will suspend all transactions associated to it). And I want my integration tests to ensure that the changes are stored in the DB anyway. :)
Concerning before(:all)
vs before(:each)
I totally agree with you. But when your integration specs takes more than 15 minutes to run, you have to speed that up. #set
will keep your specs isolated as it reloads the object from the db before every test (and RSpec run tests in isolated db transactions). You can find more examples here: https://github.com/pcreux/rspec-set/blob/master/features/lib/rspec-set.feature
There are some edge cases where the changes are propagated from one test to another though. Using before(:each)
fix this kind of issues but we could also setup transactions around contexts. Xavier Shay did that for Postgres: http://rhnh.net/2010/10/06/transactional-before-all-with-rspec-and-datamapper. I’d love to find some time to implement that in rspec-set.
Thanks again for your comment!
]]>describe Account do subject { Factory(:account) } it { should be_enabled } context "when #disable!" do before do subject.disable! end it { should be_disabled } end end
This eliminates the need for the let block altogether.
Also, the before(:all) block has a bad reputation because your tests are no longer atomic and independent. If you change part of the object in one test, it will be changed for subsequent tests in that group. For instance, the next test you add will start with the account being disabled. This may cause hard-to-find failures. Using before(:each) means being certain you’re not daisy-chaining tests that change the subject along the way.
One speed technique you might want to try is only creating objects on tests that really need them. *Most* tests can be run without the database. Your “it { should be_enabled }” test is one example.
Here’s how my version would look:
describe Account do subject { Factory.build(:account) } it { should be_enabled } context "#disable!" do it "should disable the account" do subject.should_not be_disabled subject.disable! subject.should be_disabled end end end
I’m building the account instead of creating, since the first test doesn’t need it anyway. The disable! test will still work correctly, whether it’s saving the model or not. I’m assuming it probably does save, so this approach has removed 2 out of the 3 original database writes, while preserving the clean slate that each test should have when it starts.
I don’t have a before block in the context, because I don’t abstract setup code until there is more than one test requiring the same setup. I did give it its own context, though – I always do that for methods. It makes my tests look more consistent, and the test names are formatted better. And since I agree with your approach that each spec should test as *little* as possible, no method usually stays single-test for long :)
I also ensure that disabled? is returning false before the disable! call, like you expect it to. I always test before and after my method call, to ensure that things have changed correctly, and also catches times where I setup the test wrong to begin with.
Thanks for a great site, I look forward to reading more of your work!
]]>