Unexpected before :each behavior

Hi gang.

I’ve come across what I believe to be unexpected behavior for some of my
before :each blocks, and I wonder if anyone can enlighten me as to why
this
is happening.

The surprising thing happens when I run a ‘before :each’ inside of an
each
block on a Hash. I have a #before_save hook in my ActiveRecord model.
When I
call @sample_model.save from the before block, the model runs a
uniqueness
validation. However, when I save from within the example, the save works
fine.

Below is the minimal test case I was able to put together:

class SampleClassMigration < ActiveRecord::Migration
def self.up
create_table :sample_classes do |t|
t.string :sample_field
t.string :attrib_1
t.string :attrib_2
end
end
end

class SampleClass < ActiveRecord::Base
validates_uniqueness_of :sample_field
before_save :prep
def prep
self.sample_field = ‘sample_value’
end
end

describe SampleClass do
EXAMPLES = {
:attrib_1 => ‘foo’,
:attrib_2 => ‘bar’
}
EXAMPLES.each do |key, value|
before(:each) do
@sample = SampleClass.new(key => value)
#In the passing case, this call to #save
#is moved into the ‘it’ block
@sample.save
end

it "key : #{value}\tvalue : #{key.to_s}" do
  #in the other case, I call the spec here
  @sample.should be_valid
end

end
end

It was my expectation that calling an instance method in the before
block
would be the same as calling it within the example block. Instead, I get
two
different results. When called from within the it block, both examples
pass.
When called from within the before block, they both fail:

‘SampleClass key : bar value : attrib_2’ FAILED
Expected #<SampleClass id: 2, sample_field: “sample_value”, attrib_1:
“foo”,
attrib_2: nil> to be valid, but it was not
Errors: Sample field has already been taken
./spec/models/minimal_spec.rb:20:

‘SampleClass key : foo value : attrib_1’ FAILED
Expected #<SampleClass id: 2, sample_field: “sample_value”, attrib_1:
“foo”,
attrib_2: nil> to be valid, but it was not
Errors: Sample field has already been taken
./spec/models/minimal_spec.rb:20:

I’m surprised to see that the example fails, but I’m more surprised to
see
that it fails both times. It is apparently the case that the “before”
block
is executed on every iteration of the “each” block (but that the
examples
themselves are executed later).

  1. Is this a bug?
  2. Is it a known behavior?
  3. Is there a “BDD-theoretical” better way to do something like this
    (assuming a larger hash of examples, for instance)?

I’m using:

gem ‘rails’, ‘2.3.5’
gem ‘mysql’
group :development, :test do
gem ‘database_cleaner’
gem ‘rspec-rails’, ‘1.3.2’
gem ‘rspec’, ‘1.3.0’
end

DatabaseCleaner is properly configured, and runs for many other specs.
(Indeed, it even runs correctly between steps, as demonstrated by the
case
where the specs pass).

Thanks,
Andrew Kasper

On Dec 6, 2010, at 8:12 PM, Andrew Kasper wrote:

create_table :sample_classes do |t|

def prep
before(:each) do
end
2)
‘SampleClass key : foo value : attrib_1’ FAILED
Expected #<SampleClass id: 2, sample_field: “sample_value”, attrib_1: “foo”,
attrib_2: nil> to be valid, but it was not
Errors: Sample field has already been taken
./spec/models/minimal_spec.rb:20:

I’m surprised to see that the example fails, but I’m more surprised to see that
it fails both times. It is apparently the case that the “before” block is executed
on every iteration of the “each” block (but that the examples themselves are
executed later).

  1. Is this a bug?

Nope.

  1. Is it a known behavior?

Yep. Examples are evaluated after they are all read in and
organized. Because each iteration adds a before(:each) block, the code
above has the same behavior as this:

describe SampleClass do
before(:each) do
@sample = SampleClass.new(:attrib_1 => ‘foo’)
@sample.save
end

before(:each) do
@sample = SampleClass.new(:attrib_2 => ‘bar’)
@sample.save
end

it “key : foo\tvalue : attrib_1” do
@sample.should be_valid
end

it “key : bar\tvalue : attrib_2” do
@sample.should be_valid
end
end

Seeing it this way, it is clear that there are two before(:each) hooks
that both run before each example, and that the 2nd before(:each) hook
results in a failure each time.

Make sense?

  1. Is there a “BDD-theoretical” better way to do something like this (assuming a
    larger hash of examples, for instance)?

You could create a separate context for each iteration, and each one
would have it’s own before hook:

describe SampleClass do
EXAMPLES = {
:attrib_1 => ‘foo’,
:attrib_2 => ‘bar’
}
EXAMPLES.each do |key, value|
context “with :#{key} => #{value}” do
before(:each) do
@sample = SampleClass.new(key => value)
#In the passing case, this call to #save
#is moved into the ‘it’ block
@sample.save
end

  it "key : #{value}\tvalue : #{key.to_s}" do
    #in the other case, I call the spec here
    @sample.should be_valid
  end
end

end
end

In terms theory, the trick here is that when you take short-cuts like
this you bind all the examples together. That tends to work fine until
requirements change such that one of the iterations needs to change in a
way that the others don’t.

HTH,
David

DatabaseCleaner is properly configured, and runs for many other specs. (Indeed,
it even runs correctly between steps, as demonstrated by the case where the specs
pass).

Thanks,
Andrew Kasper


rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users

Cheers,
David