Ruby-rspec how can I include a file that is a "common" before (:all) block? - ruby

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

Related

Run after block after specific test in Rspec

Is there a way I can run the after/before block after/before a specific test using labels?
I have 3 it blocks
describe "describe" do
it "test1" do
end
it "test2" do
end
after(<<what goes here??>>) do
end
end
How do I run the after block only after test2? Is that possible?
You should use contexts to do this. Something like:
describe "describe" do
context 'logged in' do
before(:each) do
# thing that happens in logged in context
end
after(:each) do
# thing that happens in logged in context
end
it "test1" do
end
end
context 'not logged in' do
# No before/after hooks here. Just beautiful test isolation
it "test2" do
end
end
end
Having if/else conditions in before/after blocks is a code smell. Don't do it that way. It'll only make your tests brittle, error prone, and hard to change.
The best way to do this is just use a context. For your example:
describe "AutomateFr33k's fr33ky tests" do
it "runs test1" do
expect(true).to be_true
end
context "do something afterwards" do
after { puts "running something after test2!" }
it "runs test2" do
expect(5).not_to eq(4)
end
end
end
Yes you can do that, have a look here
You can achieve that using metadata in rspec
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
end
describe "Skip hook demo" do
# If prior to RSpec 2.99.0.beta1
after do
puts "before hook" unless example.metadata[:skip]
end
# If RSpec 2.99.0.beta1 or later
after do |example|
puts "before hook" unless example.metadata[:skip]
end
it "will use before hook" do
end
it "will not use before hook", :skip do
end
end

Run cleanup step if any it block failed

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.

RSpec before blocks not being called before contexts or describes

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.

RSpec: Always execute before(:all) in begin/rescue

I'm writing Selenium tests, using Watir-Webdriver and RSpec, which can be a bit spotty when they're first being developed. I've run into a situation where I want to create something on the UI in before :all, however it can throw exceptions (based on timing or poor loading). When that happens I want to take a screenshot.
Here's what I have:
RSpec.configure do |config|
config.before(:all) do |group| #ExampleGroup
#browser = Watir::Browser.new $BROWSER
begin
yield #Fails on yield, there is no block
rescue StandardError => e
Utilities.create_screenshot(#browser)
raise(e)
end
end
end
I run it and get an error:
LocalJumpError: no block given (yield)
The reason I assumed yielding would work is RSpec's definition of before:
def before(*args, &block)
hooks.register :append, :before, *args, &block
end
How can I wrap the code I've put in my before :all in a begin/rescue block without having to do it on every suite?
Thanks in advanced.
The code you've written in the before hook is the &block you're referring to in RSpec::Hooks#before. The hook yields to your code, then runs your tests after the yield is complete.
As for how to make this work, I think this should do:
RSpec.configure do |config|
# ensures tests are run in order
config.order = 'defined'
# initiates Watir::Browser before all tests
config.before(:all) do
#browser = Watir::Browser.new $BROWSER
end
# executes Utilities.create_screenshot if an exception is raised by RSpec
# and the test is tagged with the :first metadata
config.around(:each) do |example|
example.run
Utilities.create_screenshot(#browser) if example.exception && example.metadata[:first]
end
end
This configuration requires the first test be tagged with metadata:
describe Thing, :first do
it "does something" do
# ...
end
end
This way, you'll only take a screenshot at the beginning of your run for a failing test, and not after every failing test. If you'd rather not mess with metadata (or prefer your tests are run in random order), you could do something like this:
RSpec.configure do |config|
# initiates Watir::Browser before all tests
config.before(:all) do
#test_count = 0
#browser = Watir::Browser.new $BROWSER
end
# executes Utilities.create_screenshot if an exception is raised by RSpec
# and the test is the first to run
config.around(:each) do |example|
#test_count += 1
example.run
Utilities.create_screenshot(#browser) if example.exception && #test_count == 1
end
end
This works for me. Instead of begin/rescue in the before :all hook,
config.after :each do
example_exceptions = []
RSpec.world.example_groups.each do |example_group|
example_group.examples.each do |example|
example_exceptions << !example.exception.nil?
end
end
has_exceptions = example_exceptions.any? {|exception| exception}
#Handle if anything has exceptions
end

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.

Resources