mongoid

How to configure Cucumber and RSpec to work with Mongoid

RSpec and Cucumber by default are setup to work with relational databases using ActiveRecord. This has been fine for the most part until so many NoSQL databases have come onto the scene. Let’s focus on getting Cucumber and RSpec working with MongoDB using Mongoid.

First you will need to setup your Rails 3 application to use Mongoid, Cucumber, RSpec, and Factory Girl. Once you have a project skeleton, you will need to setup RSpec to drop all the MongoDB collections of the project before each spec is run to ensure a clean test harness.

The best command to drop your MongoDB collections comes from Mongoid creator Durran Jordan in Mongoid’s code base:

Mongoid.master.collections.select {|c| c.name !~ /system/ }.each(&:drop)

RSpec

Here is a sample RSpec configuration which will empty your MongoDB db and also use Factory Girl to create your models. Note that config.user_transactional_fixtures is commented out.

# spec_helper.rb

ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'remarkable/active_model'
require 'remarkable/mongoid'
require 'factory_girl'

# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}

RSpec.configure do |config|
  config.mock_with :rspec

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, comment the following line or assign false
  # instead of true.
  # config.use_transactional_fixtures = true

    config.before :each do
      Mongoid.master.collections.select {|c| c.name !~ /system/ }.each(&:drop)
    end

end

Cucumber

Cucumber allows defining hooks in any file under the features/support directory. To begin setting up Cucumber, first create a file features/support/hooks.rb that will drop your MongoDB collections before each scenario.

# features/support/hooks.rb

Before do |scenario|
    Mongoid.master.collections.select {|c| c.name !~ /system/ }.each(&:drop)
end

I also find that using Factory Girl for my Cucumber scenarios makes my life a lot easier. Below is an example features/support/env.rb file which will import all your factories. It will also provide some already defined step definitions by the guys at thoughtbot. Be sure to read about them in their post gimme three steps.

# env.rb

ENV["RAILS_ENV"] ||= "test"
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')

require 'cucumber/formatter/unicode'
require 'cucumber/rails/rspec'
require 'cucumber/rails/world'
require 'cucumber/web/tableish'

require 'capybara/rails'
require 'capybara/cucumber'
require 'capybara/session'
require 'cucumber/rails/capybara_javascript_emulation'

Capybara.default_selector = :css

ActionController::Base.allow_rescue = false

require 'factory_girl'
require 'factory_girl/step_definitions'
Dir[File.expand_path(File.join(File.dirname(__FILE__),'..','..',
  'spec','factories','*.rb'))].each {|f| require f}

All the step definitions from factory_girl/step_definitions work except for “create record & set one attribute”:

Given an author exists with an email of "author@example.com"

The reason for this is because in the Factory girl code, theere is a check that determines if the given model responds to :columns. To get around this, add the following code to a new file features/step_definitions/mongoid_steps.rb:

# features/step_definitions/mongoid_steps.rb

Given /^an? (.+) exists with an? (.+) of "([^"]*)"$/ do |model, field, value|
  factory_name = model.gsub(' ', '_')
  Factory factory_name, field => value
end

The last thing to do is stub out the rake task for db:test:prepare. The Cucumber rake tasks are still dependent on the ActiveRecord test workflow and will attempt to load the db schema into the test db. Create the  file lib/tasks/mongo.rake to get around this:

Update: As of version 2.0.0.beta.14 of mongoid, a stubbed rake task for db:test:clone is included.

# lib/tasks/mongo.rake

namespace :db do
  namespace :test do
    task :prepare do
      # Stub out for MongoDB
    end
  end
end

Finally, if you don’t want to do this everytime you setup an application, I’ve updated rails-templater to do the above steps automatically.