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.

:trollface:

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:

app/
├── 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 < Struct.new(:difference, :user, :model_type, :model_id, :action)
  def save!
    @persisted = true
  end

  def persisted?
    @persisted
  end
end

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`
end

along with a speedy spec:

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

describe Journal do
  # examples here...
end

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
      Pathname.new(File.expand_path("#{__dir__}/.."))
    end

    def self.env
      'test'
    end

    def self.logger
      @logger ||= begin
        require 'logger'
        Logger.new(root.join("log/#{env}.log"))
      end
    end

    def self.cache
      nil
    end

    def self.fake?
      true
    end

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

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

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

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
end

ActiveRecord::Migration.maintain_test_schema! # rails 4.1 only

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

Conclusion

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

wpDiscuz