[rspec-users] test for "lambda {...}.should change(x, y).by(z)" failing.. ?

David Chelimsky dchelimsky at gmail.com
Thu Nov 24 13:21:07 EST 2011


On Nov 23, 2011, at 3:33 PM, Patrick J. Collins wrote:

> I wrote a test that looked like this:
> 
> 	it "increases the user's reputation" do
> 		lambda { @comment.update_attribute(:helpful, true) }.should change(@seller.reload, :reputation_score).by(Event.reputation_change_for(:mark_helpful))

The change matcher has several forms, including:

  lambda { ... }.should change(object, method).by(amount)
  lambda { ... }.should change { object.method }.by(amount)

Your example uses the former, which results in the following (roughly):

  receiver = @seller.reload
  value_before = receiver.reputation_score
  { @comment.update_attribute(:helpful, true) }.call
  value_after = receiver.reputation_score
  (value_after - value_before).should eq(Event.reputation_change_for(:mark_helpful))

As you can see, @seller.reload is only evaluated once, and its reputation score is going to be the same both times. If you want @seller.reload eval'd before and after, then you have to use the block form:

  lambda { @comment.update_attribute(:helpful, true) }.
    should change {@seller.reload.reputation_score }.
    by(Event.reputation_change_for(:mark_helpful))

Tangent: this is testing two things - @seller.reputation_score and Event.reputation_change_for(:mark_helpful). If either is failing to work correctly, this example won't tell you which. I'd recommend sticking to literals in expectations:

  lambda { @comment.update_attribute(:helpful, true) }.
    should change {@seller.reload.reputation_score }.by(3)

HTH,
David

> 	end
> 
> And I am getting this error:
>  1) Comment comments on posts marking a comment as helpful increases the user's reputation
>     Failure/Error: lambda { @comment.update_attribute(:helpful, true) }.should change(@seller.reload, :reputation_score).by(Event.reputation_change_for(:mark_helpful))
>       reputation_score should have been changed by 3, but was changed by 0
> 
> --
> 
> The way the actual code works is, I have a comment observer that does:
> 
> 	def after_update(comment)
> 		Event.create_for_user(comment.user, :mark_helpful)
> 	end
> 
> And event.rb does something like:
> 
> 	def create_for_user(user, event_type)
> 		create!(:user => user, :reputation_change => Event::SCORES[event_type])
> 	end
> 
> The user model has an before_save callback which does:
> 
> 	def sum_points
> 		self.reputation_score = events.sum(:reputation_change)
> 		self.points = events.sum(:points_change)
> 	end
> 
> 
> ---
> 
> Anyway, so this test fails, and I am not sure why...  If I write it in a
> slightly less-cool way:
> 
> 	it "increases the user's reputation" do
> 		@seller.reputation_score.should == 0
> 		@comment.update_attribute(:helpful, true)
> 		@seller.reload.reputation_score.should == Event.reputation_change_for(:mark_helpful)
> 	end
> 
> Then it passes...  If I throw in a debugger statement in there and manually
> call the code, the reputation_score does indeed increase......  So I am
> confused why the lambda {}.change thing isn't working?
> 
> Thanks.
> 
> Patrick J. Collins
> http://collinatorstudios.com



More information about the rspec-users mailing list