Disconnecting RSpec from the Database
Recently I spent some time away from RSpec and I did miss it, but one thing especially liked about the testing framework I lived in for 10 months was that it had two ways to test anything you liked: One that hit the database and one that did not. We used UnitRecord to disconnect unit tests from the database (I should mention that we defined unit tests as tests that didn't hit the db and functional tests as those that did). So the vast majority of our tests didn't really need to hit the db and ran very fast. Sometimes we wanted or needed to hit the db and we did (only occasionally in model tests, more often in controller tests, and we kept logic out of the view and only tested views at the Selenium level). It was pretty cool and I'd like to thank the team who set it up before I got there.
There's been a lot of talk about whether model, view, or controller tests should hit the db but I've found that there's enough exceptions that it's worth just saying that you should try not to hit the db when possible for speed's sake and then when the test becomes 20 lines long because of all the mocking/stubbing required, go ahead and hit it. That's all well and good, but how do you disconnect the db from an RSpec example?
I, personally, have had success with NullDB. NullDB is the null object pattern applied to databases. Which is a fancy way of saying that it swallows up any call that would go to the db and returns a nil. So you mock/stub all you like, but if you forget or just don't care about a particular call then don't worry because nothing goes to the db and nil is returned (which, surprisingly, is more than enough in most situations). On the NullDB page I've linked to, the author provides a way to universally disconnect RSpec from the db, but, as I've already said, I'm a bit pragmatic when it comes to disconnection so In my spec_helper.rb I define something like this:
and then in any "Describe" block where I'd like to disconnect the db I do this:
If I want to hit the db I just don't include Disconnected.
Now since NullDB fails silently I've heard tell of problems with it being hard to debug (although I have yet to experience these problems). Any spec/test/example that just isn't behaving properly can be exiled to the 'hit the database' area. Also I should mention that when I tried to use NullDB with Rails 1.2.6 I got a bunch of errors but it worked fine with 2.0.2. If you are looking for a more vigorous enforcement of 'don't touch the db in unit tests' then UnitRecord may be for you. I thought RSpec and UnitRecord didn't play nice, but my fellow Obtivian, Ryan Platte informs me that he's got it working. So I'm calling him out to post about disconnecting the RSpec with UnitRecord.
There's been a lot of talk about whether model, view, or controller tests should hit the db but I've found that there's enough exceptions that it's worth just saying that you should try not to hit the db when possible for speed's sake and then when the test becomes 20 lines long because of all the mocking/stubbing required, go ahead and hit it. That's all well and good, but how do you disconnect the db from an RSpec example?
I, personally, have had success with NullDB. NullDB is the null object pattern applied to databases. Which is a fancy way of saying that it swallows up any call that would go to the db and returns a nil. So you mock/stub all you like, but if you forget or just don't care about a particular call then don't worry because nothing goes to the db and nil is returned (which, surprisingly, is more than enough in most situations). On the NullDB page I've linked to, the author provides a way to universally disconnect RSpec from the db, but, as I've already said, I'm a bit pragmatic when it comes to disconnection so In my spec_helper.rb I define something like this:
1 share_as :Disconnected do
2
3 before :all do
4 ActiveRecord::Base.establish_connection(:adapter => :nulldb)
5 end
6
7 after :all do
8 ActiveRecord::Base.establish_connection(:test)
9 end
10 end
1 describe User do
2 include Disconnected
3 # 'it' blocks or
4 # more describes
5 end
Now since NullDB fails silently I've heard tell of problems with it being hard to debug (although I have yet to experience these problems). Any spec/test/example that just isn't behaving properly can be exiled to the 'hit the database' area. Also I should mention that when I tried to use NullDB with Rails 1.2.6 I got a bunch of errors but it worked fine with 2.0.2. If you are looking for a more vigorous enforcement of 'don't touch the db in unit tests' then UnitRecord may be for you. I thought RSpec and UnitRecord didn't play nice, but my fellow Obtivian, Ryan Platte informs me that he's got it working. So I'm calling him out to post about disconnecting the RSpec with UnitRecord.
Comments
So (I will try to post more soon, but can't promise when) separating out a test/spec task that doesn't have db:test:prepare as a dependency is a great way to go to achieve a fast test run (as well as the other benefits to true unit testing).
http://rubyforge.org/pipermail/chicagogroup-members-list/2008-June/001387.html