How do I use an rpec shared_examples across different files? - ruby

I want to reuse this shared_examples block across different spec files. I want to extract it into a separate file, and pass in the object so it's not always user. Both things I tried failed, is it possible?
describe User do
before { #user = build_stubbed(:user) }
subject { #user }
shared_examples 'a required value' do |key| # trivial example, I know
it "can't be nil" do
#user.send("#{key}=", nil)
#user.should_not be_valid
end
end
describe 'name'
it_behaves_like 'a required value', :name
end
end

Just require the other file. shared_examples work at the top level, so once defined they are always available; so be careful of naming conflicts.
A lot of RSpec users will put the shared example in spec/support/shared_examples/FILENAME.rb. Then in spec/spec_helper.rb have:
Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
Or on Rails projects
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
That is listed in the 'CONVENTIONS' section of the shared example docs.

Related

Passing an object as subject to rspec

I am running rspec tests on a catalog object from within a Ruby app, using Rspec::Core::Runner::run:
File.open('/tmp/catalog', 'w') do |out|
YAML.dump(catalog, out)
end
...
unless RSpec::Core::Runner::run(spec_dirs, $stderr, out) == 0
raise Puppet::Error, "Unit tests failed:\n#{out.string}"
end
(The full code can be found at https://github.com/camptocamp/puppet-spec/blob/master/lib/puppet/indirector/catalog/rest_spec.rb)
In order to pass the object I want to test, I dump it as YAML to a file (currently /tmp/catalog) and load it as subject in my tests:
describe 'notrun' do
subject { YAML.load_file('/tmp/catalog') }
it { should contain_package('ppet') }
end
Is there a way I could pass the catalog object as subject to my tests without dumping it to a file?
I am not very clear as to what exactly you are trying to achieve but from my understanding I feel that using a before(:each) hook might be of use to you. You can define variables in this block that are available to all the stories in that scope.
Here is an example:
require "rspec/expectations"
class Thing
def widgets
#widgets ||= []
end
end
describe Thing do
before(:each) do
#thing = Thing.new
end
describe "initialized in before(:each)" do
it "has 0 widgets" do
# #thing is available here
#thing.should have(0).widgets
end
it "can get accept new widgets" do
#thing.widgets << Object.new
end
it "does not share state across examples" do
#thing.should have(0).widgets
end
end
end
You can find more details at:
https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#define-before(:each)-block

Include Rspec examples with variable

I have a few sets of rspecs that all include some shared examples. I would like those shared examples to then include other shared examples, if the original spec had some variable set. Basically this is what I'm trying to do.
Example:
File: spec/test_spec.rb
describe 'some thing' do
let(:some_feature) { true }
describe 'some tests' do
include_examples "shared_tests"
end
end
File spec/shared/shared_tests.rb
shared_examples_for "shared_tests" do
include_examples "feature_specific_tests" if some_feature
end
As expected, this is throwing an error like this:
undefined local variable or method `some_feature`
Is there a way to do this? I thought perhaps I could define #some_feature in a before(:all) block and then use if #some_feature in the shared_examples, but that is always nil.
Rewriting the answer to make it a little clearer:
You had this:
File: spec/test_spec.rb
describe 'some thing' do
let(:some_feature) { true }
describe 'some tests' do
include_examples "shared_tests"
end
end
File spec/shared/shared_tests.rb
shared_examples_for "shared_tests" do
include_examples "feature_specific_tests" if some_feature
end
Change it to:
File: spec/test_spec.rb
describe 'some thing' do
describe 'some tests' do
include_examples "shared_tests" do
let(:some_feature) { true }
end
end
end
File spec/shared/shared_tests.rb
shared_examples "shared_tests" do
if some_feature
it_should_behave_like "feature_specific_tests"
end
# rest of your tests for shared example group
# 'a logged in registered user goes here
end
And it'll all work nicely :-)

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
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 }
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: 'admin#example.org')
end
end
RSpec.configure do |config|
config.include SpecUsers
end
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.
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
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.

rspec shared_context and include_context for all specs

I'm trying to define a few let's and before hooks that will run globally for all my specs by including them in a separate file using the Rspec configuration block.
I tried something like:
module Helpers
def self.included(base)
base.let(:x){ "x" }
base.before(:all){ puts "x: #{x}" }
end
end
Rspec.configure{|c| c.include Helpers }
but this doesn't work as expected. The before(:all) doesn't just run before each main example group, but each nested one as well.
Then I found out about shared_context and it appears to be exactly what I want.
My open problem however is that I can't figure out how to share a context amongst ALL of my specs. The docs only reference include_context within a specific spec.
Can anyone tell me how I can achieve this behavior in a global manner? I'm aware that I can define global before hooks in my spec_helper but I can't seem to use let. I'd like a single place that I can define both of these things and not pollute my spec helper, but just include it instead.
I tried to reproduce your error, but failed.
# spec_helper.rb
require 'support/global_helpers'
RSpec.configure do |config|
config.include MyApp::GlobalHelpers
end
# support/global_helpers.rb
module MyApp
module GlobalHelpers
def self.included(base)
base.let(:beer) { :good }
base.before(:all) { #bottles = 10 }
end
end
end
# beer_spec.rb
require 'spec_helper'
describe "Brewery" do
it "makes good stuff" do
beer.should be :good
end
it "makes not too much bottles" do
#bottles.should == 10
end
context "when tasting beer" do
before(:all) do
#bottles -= 1
end
it "still produces good stuff" do
beer.should be :good
end
it "spends some beer on degusting" do
#bottles.should == 9
end
end
end
https://gist.github.com/2283634
When I wrote something like base.before(:all) { p 'global before'; #bottles = 10 }, I got exactly one line in spec output.
Notice that I didn't try to modify instance variables inside an example, because it wouldn't work anyway (well, actually you can modify instance variables, if it's a hash or array). Moreover, even if you change before(:all) in nested example group to before(:each), there will be still 9 bottles in each example.

How to access the describe text in rspec

I'm writing some specs that test the template files in a gem that has generators for Rails. I'd love to access to "admin_layout.html.erb" in the rspec spec below:
require 'spec_helper'
describe "admin_layout.html.erb" do
it "has page title Admin" do
HERES WHERE I WOULD LOVE TO HAVE ACCESS TO "admin_layout.html.erb" AS A VARIABLE
end
end
You can use self.class.description to get this info:
it "has page title Admin" do
layout = self.class.description
# => "admin_layout.html.erb"
end
However, keep in mind this will only put out the first parent's description. So if you have contexts in your describe block, then the examples within the contexts would give the context name for self.class instead of the describe block's name. In that case, you could use metadata:
describe "admin_layout.html.erb", :layout => "admin_layout.html.erb"
context "foo" do
it "has page title Admin" do
layout = example.metadata[:layout]
end
end
end
In case you want the top-level description, you can use self.class.top_level_description:
RSpec.describe "Foo", type: :model do
context "bar" do
it "is part of Foo" do
self.class.top_level_description
# => "Foo"
end
end
end

Resources