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 :-)
Related
Does anyone know a way to skip multiple examples within a group, without duplicating the skip statement between them?
For example, given this test:
describe 'some feature' do
it 'should do something' do
...
end
it 'should do something else too' do
...
end
end
a skip doesn't work if placed before the first example, like so:
describe 'some feature' do
skip 'I would like to skip both with one statement'
it 'should do something' do
...
end
it 'should do something else too' do
...
end
end
An ideal solution would allow me to skip at any level of the example structure (describe/feature, context, and scenario/it) and would skip all children of that level of the hierarchy.
In other words, would allow me to do:
describe 'some feature' do
it 'should do something' do
...
end
it 'should do something else too' do
skip 'just one of these for now'
...
end
end
AND
describe 'some feature' do
skip 'everything within this describe block'
it 'should do something' do
...
end
it 'should do something else too' do
...
end
end
AS WELL AS
describe 'some feature' do
context 'such and such' do
skip 'just this context'
it 'should do something' do
...
end
it 'should do something else too' do
...
end
it 'but do not skip this one' do
...
end
end
As described in the documentation, you can use metadata to skip a context.
describe 'some feature', :skip do
it 'should do something' do
# This example is skipped
end
it 'should do something else too' do
# This example is skipped as well
end
end
When one of my it blocks fails, I want to run a cleanup step. When all of the it blocks succeed I don't want to run the cleanup step.
RSpec.describe 'my describe' do
it 'first it' do
logic_that_might_fail
end
it 'second it' do
logic_that_might_fail
end
after(:all) do
cleanup_logic if ONE_OF_THE_ITS_FAILED
end
end
How do I implement ONE_OF_THE_ITS_FAILED?
Not sure if RSpec provides something out of the box, but this would work:
RSpec.describe 'my describe' do
before(:all) do
#exceptions = []
end
after(:each) do |example|
#exceptions << example.exception
end
after(:all) do |a|
cleanup_logic if #exceptions.any?
end
# ...
end
I digged a little into the RSpec Code and found a way to monkey patch the RSpec Reporter class. Put this into your spec_helper.rb:
class RSpecHook
class << self
attr_accessor :hooked
end
def example_failed(example)
# Code goes here
end
end
module FailureDetection
def register_listener(listener, *notifications)
super
return if ::RSpecHook.hooked
#listeners[:example_failed] << ::RSpecHook.new
::RSpecHook.hooked = true
end
end
RSpec::Core::Reporter.prepend FailureDetection
Of course it gets a little more complex if you wish to execute different callbacks depending on the spec you're running at the moment.
Anyway, this way you do not have to mess up your testing code with exceptions or counters to detect failures.
Given the following code:
RSpec.configure do |config|
config.before(:all) { puts 'before all' }
config.before(:suite) { puts 'before suite'}
config.before(:context) { puts 'before context'}
config.before(:each) { puts 'before each'}
end
RSpec.describe "SomeClass" do
it 'matches some regex' do
puts 'in first it block'
expect('some string').to match(/.*/)
end
describe 'some group of tests' do
puts 'in some group'
context 'when some thing happens' do
puts 'in context'
it 'does something' do
expect(true).to be_truthy
end
end
end
end
I would expect the following output:
before suite
before all
before context
before each
in some group
in context
in first it block
.before each
But instead I get:
in some group
in context
before suite
before all
before context
before each
in first it block
.before each
Meaning that context or describe gets run before any before configuration I've set up.
I expect it to be the first output because of what I've read here and here.
What do I do when I absolutely need code to run before absolutely anything else in the test files? Including (nested) context or describes? And why doesn't it work the way I expect?
Note: I see the same behavior when I include the before :something statements within the scope of the uppermost describe.
(This question is similar to this question, but not the same. I would like to know why my tests are running this way and what the proper RSpec convention is to run a piece of code before absolutely anything else.)
Version info:
RSpec 3.6
- rspec-core 3.6.0
- rspec-expectations 3.6.0
- rspec-mocks 3.6.0
- rspec-support 3.6.0
UPDATE:
It may be helpful to know some context: I'm writing selenium front end automated tests using the selenium-webdriver gem. Before any and all it blocks run, I need to call a function called navigate() (in order to take me to the web page I'm writing the tests for, this function takes about 30 seconds to run because it takes me through two login pages before it gets to where it needs to go) to be called and complete before anything else happens. In my RSpec file I'm using before blocks in an attempt to make this happen, however rspec keeps running tests before the before blocks, and failing.
If you were to put puts "in some group" and puts "in context" into before(:all) blocks, then the output is closer to what you're expecting.
RSpec.configure do |config|
config.before(:all) { puts 'before all' }
config.before(:suite) { puts 'before suite'}
config.before(:context) { puts 'before context'}
config.before(:each) { puts 'before each'}
end
RSpec.describe "SomeClass" do
it 'matches some regex' do
puts 'in first it block'
expect('some string').to match(/.*/)
end
describe 'some group of tests' do
before(:all) { puts 'in some group' }
context 'when some thing happens' do
before(:all) { puts 'in context' }
it 'does something' do
expect(true).to be_truthy
end
end
end
end
outputs
before suite
before all
before context
before each
in first it block
.in some group
in context
before each
.
or, if you did before(:each) you would get
before suite
before all
before context
before each
in first it block
.before each
in some group
in context
.
The reason for the current output is your puts statements for "in some group" and "in context" are being executed when the file is being parsed, not waiting for RSpec at all. If we gave a different example, without Rspec in the mix, imagine we had a file with just
class SomeClass
puts "in class"
def do_something
puts "doing something"
end
end
if we load that file into an irb session or run it on the command line with ruby, we would see "in class" output in the console even though we haven't done anything with that class.
I have the following code:
describe Line do
before :all do
puts "In #{self.class.description}"
end
...
which works fine.
I would like that code (just the three lines) to be in a helper file (called header.rb) but when I try that with:
load "header.rb"
I get:
undefined method `before' for main:Object (NoMethodError)
I also tried require_relative and got the same result.
Option 1: If this applys to all your tests, you can set it in configure
# spec/spec_helper.rb
RSpec.configure do |config|
config.before(:all) do
puts "In #{self.class.description}"
end
config.before(:all) do
puts "More stuff can be added in chain"
end
end
Option 2: If you only want to use it in some tests and the context would be a bit more complex, you can use shared_context
# spec/support/some_shared_context.rb
shared_context "putting class" do
before :all do
puts "In #{self.class.description}"
end
end
# Test file
require 'spec/support/some_shared_context.rb'
describe "test foo" do
include_context "putting class"
# normal test code
end
More about shared_context: https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-context
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.