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
> [email protected]
> http://rubyforge.org/mailman/listinfo/rspec-users
Cheers,
David
_______________________________________________
rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users