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@rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users

Cheers,
David



_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to