Mocking Rails, for SPEED

DISCLAIMER: This article is by no means related to the recent quarrel about TDD’s death (be it presumed or actual), nor to this article or these hangouts.


Now that we’re clear let us get to the real title:

How I test Rails stuff without loading Rails and learned to use mkdir

Chapter 1: mkdir1

this chapter is quite brief and straightforward…

Starting from Rails 3 every folder you create under app will be used for autoloading, this means that you can group files by concern2 instead of grouping them by MVC role, or you maybe want to add new “roles”.

For example the app I’m currently working on contains looks like this:

├── admin
├── assets
├── authentication
├── authorization
├── controllers
├── forms
├── helpers
├── journaling
├── mailers
├── models
├── proposals
├── utils
└── views

The journaling folder contains journal.rb (a simple Ruby class) and journal_entry (an ActiveRecord model).

Chapter 2: Rails not required

Talking about that Journal, it doesn’t really need Rails to be used, it’s ok with any JournalEntry-ish object that exposes this API:

class FauxJournalEntryClass <, :user, :model_type, :model_id, :action)
  def save!
    @persisted = true

  def persisted?

So it can be used and tested without loading rails, that gives opportunity to have the corresponding spec to give us really fast feedback. Enter a lighter version of our spec_helper:

# spec/spec_helper.rb
RSpec.configure do |config|
  # yada, yada, rspec default config from `rspec --init`

along with a speedy spec:

require 'light_spec_helper'
require File.expand_path('../../app/journal/journal', __FILE__)

describe Journal do
  # examples here...

and have the normal spec_helper to load the light one:

# spec/spec_helper.rb
require 'light_spec_helper'
# stuff coming from `rails generate rspec:install`...

Chapter 3: Mocking Rails

…without making fun of it

With time i often found myself in the need to add a logger, to check current environment or to know the app’s root from this kind of plain classes.

In a Rails app the obvious choices are Rails.logger, Rails.env and Rails.root. In addition I really can’t stand that File.expand_path and light_spec_helper, they’re just ugly.

My solution was to:

  • have a faux Rails constant that quacks like Rails in all those handy methods
  • add the most probable load paths into the light spec_helper
  • rename the the rails-ready spec_helper to rails_helper and keep the light helper as the default one

Here’s how it looks:

# spec/spec_helper.rb
require 'bundler/setup'
require 'pathname'

unless defined? Rails
  module Rails
    def self.root"#{__dir__}/.."))

    def self.env

    def self.logger
      @logger ||= begin
        require 'logger'"log/#{env}.log"))

    def self.cache

    def self.fake?

    def self.implode!
      class << self
        %i[fake? cache logger env root implode!].each do |m|
          remove_method m

$:.unshift *Dir[File.expand_path("#{Rails.root}/{app/*,lib}")]

RSpec.configure do |config|
  # yada, yada, rspec default config from `rspec --init`

Notice anything of interest?
I bet you already guessed what Rails.implode! does…

Here’s the shiny new rails_helper:

ENV["RAILS_ENV"] ||= 'test'
require 'spec_helper'
Rails.implode! if Rails.respond_to? :implode!

require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join('spec/support/**/*.rb')].each do |f|
  f = Pathname(f).relative_path_from(Rails.root.join('spec'))
  require f

ActiveRecord::Migration.maintain_test_schema! # rails 4.1 only

RSpec.configure do |config|
  # stuff coming from `rails generate rspec:install`...


The introduction of Spring in Rails 4 has actually made all this stuff useless, running RSpec through it makes it blazingly fast.

Sorry if I wasted your time and I didn’t even make you smile.

  1. I don’t actually use mkdir, I know how to use my editor 
  2. Not those concerns 

Leave a Reply

Please Login to comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.