[rspec-users] Unexpected before :each behavior.

David Chelimsky dchelimsky at gmail.com
Mon Dec 6 21:51:46 EST 2010


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

> 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:
> 
> 1)
> '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:
> 
> 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.

> 2) 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?

> 3) 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

> 
> 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
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users

Cheers,
David



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20101206/8c51518f/attachment.html>


More information about the rspec-users mailing list