Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In what order do RSpec's before, after and around hooks run?

Tags:

ruby

rspec

As I faced some issue I decided to check in what order before and after hooks are executed. This is what I did:

require "spec_helper"

describe "The order:" do

  before(:all) {
    puts "before_all"
  }

  after(:all) {
    puts "after_all"
  }

  before(:each) {
    puts "before_each"
  }

  after(:each) {
    puts "after_each"
  }

  describe "DESC A" do
    before {
      puts "A_before"
    }
    it "A_it_1" do      
      expect(1).to eq(1)
    end
    it "A_it_2" do
      expect(1).to eq(1)
    end
  end

  describe "DESC B" do
    before {
      puts "B_before"
    }
    it "B_it_1" do      
      expect(1).to eq(1)
    end
    it "B_it_2" do
      expect(1).to eq(1)
    end
  end  
end

and what I got:

The order:

before_all

  DESC A
before_each
A_before
after_each
    A_it_1
before_each
A_before
after_each
    A_it_2
  DESC B
before_each
B_before
after_each
    B_it_1
before_each
B_before
after_each
    B_it_2

after_all

What is going on here ?? Why is after_each run before A_it_1 ?

UPDATE:

adding around(:each) is even more fun:

 around(:each) do |example|
    puts "around_in"
    example.run
    puts "around_out"
  end

and results:

The order:
before_all
  DESC A
around_in
before_each
A_before
after_each
around_out
    A_it_1
around_in
before_each
A_before
after_each
around_out
    A_it_2
  DESC B
around_in
before_each
B_before
after_each
around_out
    B_it_1
around_in
before_each
B_before
after_each
around_out
    B_it_2
after_all
like image 403
pawel7318 Avatar asked May 22 '14 15:05

pawel7318


2 Answers

Your output, and the official output documented on relishapp.com, are correct. What's happening is that rspec needs to run the after(:each)es after each example, because an exception in an after(:each) would cause the example to fail. Before rspec can display the example in the output, it needs to know whether it is green or red, which means the after(:eaches) need to be run before the example's description appears in the output.

However, if you put a puts statement in your actual example, you will see that the before(:each)es occur before it, then the example code is run (including the puts), then the after(:each)es, just as you would expect, and finally, the description of the example is output to screen.

Like you, I was also confused, until I realized that rspec printing out the example's label doesn't coincide with what it's actually doing -- the label only gets printed out once all the before(:all)s, before(:each)es, and after(:each)es are run for the example.

Note: after(:all)s get run after the example label is printed out, because they do not affect the outcome of the test (a warning is generated that an exception occurred in an after(:all) hook, but this does not make a test go red).

like image 162
pstare Avatar answered Sep 21 '22 20:09

pstare


RSpec's documentation for before and after hooks specifies the order in which they run. However, RSpec's documentation for around hooks doesn't specify the order in which they run.

This spec tests the order in which around, before and after :all and :each, and examples, execute. When I run it with rspec(-core) 2.14.8, they execute in the order you'd expect:

describe "order in which rspec around/before/after hooks run" do
  before :all do
    defined?($previous_hook).should be_false # this hook runs first
    $previous_hook = "before :all"
  end

  around :each do |example|
    $previous_hook.should == "before :all"
    $previous_hook = "around :each 1"
    example.run
    $previous_hook.should == "after :each"
    $previous_hook = "around :each 2"
  end

  before :each do
    $previous_hook.should == "around :each 1"
    $previous_hook = "before :each"
  end

  it "should not raise an exception or print anything" do
    $previous_hook.should == "before :each"
    $previous_hook = "example"
  end

  after :each do
    $previous_hook.should == "example"
    $previous_hook = "after :each"
  end

  after :all do
    # rspec ignores assertion failures and any other exceptions raised here, so all we can do is puts.
    # $previous_hook is a global because if it's an instance variable it is "before :all" at this point.
    warn "Previous hook was #{$previous_hook}, NOT around :each 2 as expected" unless $previous_hook == "around :each 2"
  end

end

Note some possibly surprising things:

  • self is different in :all and :each blocks, so I needed to use a global rather than an instance variable.
  • after :all (but not before :all) eats exceptions.
  • look at all those places .should works! Not that you'd normally want to use it there.
like image 39
Dave Schweisguth Avatar answered Sep 22 '22 20:09

Dave Schweisguth