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))
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
on 2011-11-23 23:10
on 2011-11-24 21:35
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
on 2011-11-24 23:21
> As you can see, @seller.reload is only evaluated once, and its reputation > score is going to be the same both times. Aha.. Makes perfect sense. Thanks. > 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) Hmmm.. I totally get why you say this, but part of me really hates the idea of that 3 someday changing to 5 and then my test breaking, forcing me to update the 3 in multiple places. Would it not be safe to assume that if there's a test for Event.rb verifying the behavior of Event.reputation_change_for, then it's safe to use that in a example? When I originally wrote this, I wanted to stub out the Event::SCORES constant and return a hash with just something like: { :mark_helpful => { :reputation_change => 123 } } But I couldn't figure out how to stub a constant..... Of course I could just make my test overwrite the mark helpful value of that constant like: Event:SCORES[:mark_helpful] = { :reputation_change => 123 } But that felt a little wrong so I chose to just rely on a class convenience method to return the constant's value. Any thoughts on these other approaches? Patrick J. Collins http://collinatorstudios.com
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.