p0deje
p0deje

Reputation: 4073

Automatically share context in RSpec

I want to share a memoized method between my specs. So I tried to use shared context like this

RSpec.configure do |spec|
  spec.shared_context :specs do
    let(:response) { request.execute! }
  end
end

describe 'something' do
  include_context :specs
end

It works ok. But I have about 60 spec files, so I'm forced to explicitly include context in each of them. Is there an way to automatically include shared context (or at least let definition) for all example groups in spec_helper.rb?

Something like this

RSpec.configure do |spec|
  spec.include_context :specs
end

Upvotes: 17

Views: 6500

Answers (5)

Thomas Klemm
Thomas Klemm

Reputation: 10856

In RSpec 3+, this can be achieved as follows - based on Jeremy Peterson's answer.

# spec/supprt/users.rb
module SpecUsers
  extend RSpec::SharedContext

  let(:admin_user) do
    create(:user, email: '[email protected]')
  end
end

RSpec.configure do |config|
  config.include SpecUsers
end

Upvotes: 6

Waiting for Dev...
Waiting for Dev...

Reputation: 13037

Another way to go is to automatically share examples via metadata. So:

shared_context 'a shared context', a: :b do
  let(:foo) { 'bar' }
end

describe 'an example group', a: :b do
  # I have access to 'foo' variable
end

The most common way I use it is in rspec-rails, with some shared context depending on the example group type. So if you have config.infer_spec_type_from_file_location!, you can simply do:

shared_context 'a shared context', type: :controller do
  let(:foo) { 'bar' }
end

describe SomeController do
  # I have access to 'foo' variable
end

Upvotes: 3

merqlove
merqlove

Reputation: 3784

Also if you need ability to use shared data in before blocks inside specs, as me, try to include this (if its Rails project):

module SettingsHelper
  extend ActiveSupport::Concern

  included do
    attr_reader :default_headers

    before :all do
      @default_headers = Hash[
          'HTTP_HOST' => 'test.lvh.me'
        ]
    end

    after :all do
      @default_headers = nil
    end
  end
end

RSpec.configure do |config|
  config.include SettingsHelper
end

Or try something similar, look at @threedaymonk answer.

Upvotes: 0

threedaymonk
threedaymonk

Reputation: 525

You can do it almost like that: there's a mechanism for including a module, and module inclusion has its own callback mechanism.

Suppose for example that we have a disconnected shared context that we want to use to run all our model specs without a database connection.

shared_context "disconnected"  do
  before :all do
    ActiveRecord::Base.establish_connection(adapter: :nulldb)
  end

  after :all do
    ActiveRecord::Base.establish_connection(:test)
  end
end

You can now create a module that will include that context on inclusion.

module Disconnected
  def self.included(scope)
    scope.include_context "disconnected"
  end
end

Finally, you can include that module into all specs in the normal manner (I've demonstrated doing it only for models, just to show that you can), which is almost exactly what you asked for.

RSpec.configure do |config|
  config.include Disconnected, type: :model
end

That works with rspec-core 2.13.0 and rspec-rails 2.13.0.

Upvotes: 5

Jeremy Peterson
Jeremy Peterson

Reputation: 666

You can set up global before hooks using RSpec.configure via configure-class-methods and Configuration:

RSpec.configure {|c| c.before(:all) { do_stuff }}

let is not supported in RSpec.configure, but you can set up a global let by including it in a SharedContext module and including that module using config.before:

module MyLetDeclarations
  extend RSpec::Core::SharedContext
  let(:foo) { Foo.new }
end
RSpec.configure { |c| c.include MyLetDeclarations }

Upvotes: 33

Related Questions