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