Be aware that this is considered anti-pattern in many cases. Really depends on what type of testing environment you are trying to achive more in “warning” section bellow

When it comes to RSpec every developer heavily uses before(:each) or after(:each) hooks and kinda knows that something like before(:all) exist but is not used often. Well why should it be ?

As your application grows your tests take longer and longer, and you actually discover you have far more coffee breaks than you use to.

Reason for this is obvious: Your tests are hitting database too many times.

The example

We will be demonstrating specs on simple candy that have many ingredients:

# db/migrations/2014***********_create_candies.rb class CreateCandies < ActiveRecord::Migration def change create_table :candies do |t| t.string :name end end end # db/migrations/2014***********_create_ingredients.rb class CreateIngredients < ActiveRecord::Migration def change create_table :ingredients do |t| t.integer :candy_id t.string :name end end end # app/model/candy.rb class Candy < ActiveRecord::Base has_many :ingredients end # app/model/ingredietns.rb class Candy < ActiveRecord::Base belongs_to :candy end

How to write specs (…in Utopia)

So let say you are starting new project, you can afford to avoid hitting database by using rspec-rails gem mock helpers like mock_model , via RSpec double - stub combination, or if you use Factory Girl gem via awesome helpers build and build_stubbed

RSpec Rails mock_model

# spec/models/candy_spec.rb describe Candy do let(:candy) { mock_model(Candy, id: 123, name: 'Chocolate') } describe "#name" do subject { candy.name } it { should eq 'Chocolate' } end end

Factory Girl bulid and build_stubbed

# spec/factories/candies.rb FactoryGirl.define do factory :candy do name 'Chocolate' end end # spec/factories/ingredietns.rb FactoryGirl.define do factory :ingredient do name 'Milk' end end # spec/models/candy_spec.rb describe Candy do let(:candy1) { build(:candy) } let(:candy2) { build(:candy, name: 'Jelly Beans') } describe "#id" do it 'demonstrate :id' do candy1.id # => nil candy2.id # => 1001 end end describe "#name" do it { candy1.name eq 'Chocolate' } it { candy2.name eq 'Jelly Beans' } end describe "#ingredients" do let(:ingredient) { build :ingredient } before { candy1.ingredients = [ingredient] } it { candy1.ingredients.should include ingredient } end end

This way you can associate one model into another without hitting the database ever again! …well not really. Sooner or later you will have complicated scenario that would either took long time to write/rewrite as a memory test or the database-hitting test would be easier, more readable or less fragile.

Or maybe new junior developer will create his first pull request and you wont have time explain to him the theory of survival in tests world.

Wild Wild Specs

Problem starts with someone writing the first spec that saves to database. From that point less discipline developers will be copying the code (one way or another) without truly caring that number of database queries are growing.

Basically you end up with something like this:

# spec/factories/candies.rb FactoryGirl.define do factory :candy do name 'Chocolate' trait :with_ingredients do after(:create) do |candy| create(:ingredient, candy: candy) create(:ingredient, candy: candy, name: 'Sugar') end end end end # spec/mailers/candy_mailer_spec.rb describe CandyMailer do let(:candy) { create(:candy, :with_ingredients) } describe "#send_ingredients" do let(:mail) { described_class.restock_candy_ingredients(candy)) } it { expect(mail.subject).to eq 'Ingredients for Chocolate' } it { expect(mail.body).to match '<h1>Chocolate</h1>' } it { expect(mail.body).to match '<span style="color: red">Milk<span>' } #ingredient 1 it { expect(mail.body).to match '<span style="color: red">Sugar<span>' } #ingredient 2 end end

Although this example can be written without even touching the Database, lets just assume that candy needs to be be created due to some difficult technical difficulty or association.

This way we ended up with 4 it statements, each will create not only the candy but its ingrediens as well. That means 3 SQL calls per it statement totalling 12 sql calls per this simple test file.

More you write tests related to candy that requires create instead of build or more complex your associations grow, slower your tests will get.

Before :all to rescue !

Lets look at this example from different perspective:

# spec/mailers/candy_mailer_spec.rb describe CandyMailer do attr_reader :candy before(:all) @candy = create(:candy, :with_ingredients) } end after(:all) Candy.destroy_all # if you use Database Cleaner gem I recommend usage of : # DatabaseCleaner.clean_with :deletion end describe "#send_ingredients" do let(:mail) { described_class.restock_candy_ingredients(candy)) } it { expect(mail.subject).to eq 'Ingredients for Chocolate' } it { expect(mail.body).to match '<h1>Chocolate</h1>' } it { expect(mail.body).to match '<span style="color: red">Milk<span>' } #ingredient 1 it { expect(mail.body).to match '<span style="color: red">Sugar<span>' } #ingredient 2 end end

This way we will trigger 3 SQL calls in the beginning of spec and each it statement will work with those resources without more Database calls.

One thing to remmember is that you may need to wipe your database after this particular spec description/context finish so that next spec context wont have database records created in this instance ( look at the after(:all) block ).

Example

The other day I was working on large change in the application I’m currently working. Because tight deadline I didn’t refactor the tests to the way how this article is proposing (I was using lot of let(:user) { create :user , ... } and before(:each) { ... } ). The perfmance of test suite was like this:

I’m triggering just 4 - 6 create queries per test.

After things settled down I’ve manage to find some time and do the refactoring to before(:all) test suite was like:

I’m not introducing any other code / specs changes, just the before(:all) change

Warning

One note here. I’m fully aware that this approach violates the principles of isolated tests. I fully respect that ideology but problem is that sometimes you are dealing with test environment where test isolation is already dead and you are trying to speed the tests without spending month refactoring entire test suite.

For example you may want to run your tests in Parallel. Then this solution would not work as you need isolated tests for that.

But think about this solution as something you would run after your isolated tests are finished:

rspec --tag=~non-isolated-tests #run isolated tests, skip "non-isolated" test rspec --tag=non-isolated-tests #now run all tests that are not isolated

https://www.relishapp.com/rspec/rspec-core/v/2-4/docs/command-line/tag-option

I Do recommend to watch entire Martin Fowler talk to understand bottlenecks of bad test design.

Keywords: Rails 4, Ruby 2, RSpec, before all, after all, test-suite, TDD