This article is quite old.Time flies when you're having fun. I've been writing for my blog for a long time. Stuff changes fast, especially in the Ruby world. That's why I've put this warning on old posts. The article might still be valid, though.
Testing ActiveRecord doesn't have to be slow. With some clever loading you can require only the parts that you need and it isn't even that difficult.
Another reason might be that you're using ActiveRecord without Rails. This might be in another framework like Sinatra, or in a gem. Without Rails you might be lost a little on how to set up ActiveRecord.
First, install the gems you'll need. This should be enough:
gem install activerecord rspec sqlite3
I'm using a really simple spec to get up and running. I'm even defining the model inside the spec itself, just because it's easier to get started:
# spec/widget_spec.rb class Widget < ActiveRecord::Base validates_presence_of :name end describe Widget do it "requires a name" do subject.name = "" subject.should have(1).error_on(:name) subject.name = "Foo" subject.should have(:no).errors_on(:name) end end
Run this spec on its own, simply by running:
You'll see that it doesn't even know ActiveRecord yet.
Let's create a support file for specs that need ActiveRecord. I put this in
For now, this file will just require ActiveRecord for us:
# Create the file spec/support/active_record.rb: require 'active_record'
We need ActiveRecord before we define our model, so require the support file at the very top of your spec:
# Prepend to spec/widget_spec.rb: require 'support/active_record'
RSpec automatically adds the
spec directory to the load paths, so
these files can be required easily.
Connecting with the database
When we run the spec now, it should tell us that ActiveRecord has no connection with the database. I'm going for a SQLite database in memory, so let's define the connection in the support file.
# Append to spec/support/active_record.rb: ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
When we run it now, it will tell us that we don't have a table yet.
If you have migrations, you can run them with just one simple line:
# Append to spec/support/active_record.rb: ActiveRecord::Migrator.up "db/migrate"
You can also just create the migration inline, which is probably even simpler:
# Append to spec/support/active_record.rb: ActiveRecord::Migration.create_table :widgets do |t| t.string :name t.timestamps end
If you have a
schema.rb file, you can simply load it:
# Append to spec/support/active_record.rb: load "path/to/db/schema.rb"
Getting some RSpec helpers
When we run it now, we'll see the familiar output of the migrations. The next
error we get is an undefined method
If your project already depends on
rspec-rails, you can simply require the file:
# Append to spec/support/active_record.rb: require 'rspec/rails/extensions/active_record/base'
If you're not in a Rails project (but in a Gem or Engine), you might not want
to have rspec-rails as a dependency, because it adds a lot of extra
dependencies. You can just define the
error_on method yourself, by
# Append to spec/support/active_record.rb: module ActiveModel::Validations # Extension to enhance `should have` on AR Model instances. Calls # model.valid? in order to prepare the object's errors object. # # You can also use this to specify the content of the error messages. # # @example # # model.should have(:no).errors_on(:attribute) # model.should have(1).error_on(:attribute) # model.should have(n).errors_on(:attribute) # # model.errors_on(:attribute).should include("can't be blank") def errors_on(attribute) self.valid? [self.errors[attribute]].flatten.compact end alias :error_on :errors_on end
This should be enough to get your spec running! And look how fast it is! Clearly, loading ActiveRecord isn't what makes Rails startup time slow.
The last part you'll want is to run your specs in transactions, so that one spec doesn't influence the others. To do this, we'll make a simple around filter:
# Append to spec/support/active_record.rb: RSpec.configure do |config| config.around do |example| ActiveRecord::Base.transaction do example.run raise ActiveRecord::Rollback end end end
ActiveRecord::Rollback exception will be caught by the transaction block
and roll back all the database changes after each spec.
Now, you might want to clean it up a little more, but this should be enough to getting you started testing your ActiveRecord classes in (relative) isolation. And the cool thing is, running these specs hardly takes any time!
If you have any tips, please share them in the comment-section below.
If you want to know more about how ActiveRecord works, I can recommended this good series of posts by Robin Roestenburg. In the course of several blog posts he dives into the inner workings of ActiveRecord, even refactoring parts of it.
Next up: Testing controllers in isolation. Stay tuned!