Reputation: 5931
Let me quickly explain:
I'm testing rails 4
model concern with Rspec
. I made shared_examples_for Uuidable
(Uuidable
is concern name)
Concern is really short and easy to understand so I'm pasing code below:
module Uuidable
extend ActiveSupport::Concern
included do
before_create :generate_uuid
end
protected
def generate_uuid
self.uuid = loop do
uuid = SecureRandom.uuid
break uuid unless uuid_exists?
end
end
private
def uuid_exists?
self.class.exists?(uuid: uuid)
end
end
the problem is when I'm testing inside RSpec
:
let(:model) { FactoryGirl.build(described_class) }
it 'should not generate same uuid' do
SecureRandom.stub(:uuid).and_return("c640f32c-e21b-44ea-913e-7041fdb6de85", "c640f32c-e21b-44ea-913e-7041fdb6de85", "e93f0130-3a81-406f-8871-609d89ae0850")
model.save
expect(FactoryGirl.create(described_class).uuid).not_to eq(model.uuid)
end
Things gets really funny now:
Why? Expect
fails. Reason? Oh well..
So to debug it I added binding.pry
inside
def generate_uuid
self.uuid = loop do
uuid = SecureRandom.uuid
binding.pry
break uuid unless uuid_exists?
end
end
let me share console output via screenshot:
Let me quickly guide you through it:
First =>
comes from model.save
. First SecureRandom.stub(:uuid)
was called. Everything is fine so I do exit
Second =>
comes from FactoryGirl.create(described_class)
. stubbed SecureRandom.uuid
should return same uuid as it did in previous model and indeed it does *(call [1]
and [1]
).
What I expect to happen now: uuid_exists?
return true
so loop restarts and get 3rd stubbed SecureRandom.uuid
and then save model. But in call [2] uuid_exists?
we got... false
?!?! WHAAAT?
ok, maybe something went wrong with creating last model so I'll just manually call code from uuid_exists?
.
WHAAAAAT?! it returns true
!!!!. Couldn't believe so I redefined whole method in call [4]
. It still returns false
. Then few manual tests to ensure does last model exists and has uuid. it DOES
After call [10]
test fails so it means it didn't go through loop
again.
Can anyone please explain this strange behaviour?
When I changed uuid_exists?
call in unless
statement to self.class.exists?(uuid: uuid)
it passes. I'm so CONFUSED
As requested I'm pasting text from console:
19:51:34 - INFO - Running: spec/models/review_spec.rb
*********.
From: /Users/filip/programming/service_matters/app/models/concerns/uuidable.rb @ line 13 Uuidable#generate_uuid:
10: def generate_uuid
11: self.uuid = loop do
12: uuid = SecureRandom.uuid
=> 13: binding.pry
14: break uuid unless uuid_exists?
15: end
16: end
[1] pry(#<Review>)> uuid
=> "c640f32c-e21b-44ea-913e-7041fdb6de85"
[2] pry(#<Review>)> exit
From: /Users/filip/programming/service_matters/app/models/concerns/uuidable.rb @ line 13 Uuidable#generate_uuid:
10: def generate_uuid
11: self.uuid = loop do
12: uuid = SecureRandom.uuid
=> 13: binding.pry
14: break uuid unless uuid_exists?
15: end
16: end
[1] pry(#<Review>)> uuid
=> "c640f32c-e21b-44ea-913e-7041fdb6de85"
[2] pry(#<Review>)> uuid_exists?
=> false
[3] pry(#<Review>)> self.class.exists?(uuid: uuid)
=> true
[4] pry(#<Review>)> def uuid_exists?
[4] pry(#<Review>)* self.class.exists?(uuid: uuid)
[4] pry(#<Review>)* end
=> :uuid_exists?
[5] pry(#<Review>)> uuid_exists?
=> false
[6] pry(#<Review>)> self
=> #<Review id: nil, organization_id: 3, attitude: 1, feedback_action: "Refund", feedback_details: "Normandia SV2 didn't have AI module", approved: false, uuid: nil, feedback_date: "2014-08-18 17:51:58", created_at: "2014-08-28 17:51:58", updated_at: "2014-08-28 17:51:58", category_id: 3>
[7] pry(#<Review>)> self.uuid
=> nil
[8] pry(#<Review>)> Review.count
=> 1
[9] pry(#<Review>)> Review.first.uuid
=> "c640f32c-e21b-44ea-913e-7041fdb6de85"
[10] pry(#<Review>)> exit
F
Upvotes: 0
Views: 603
Reputation: 84114
Your uuid_exists?
method checks for the presence of a row whose uuid is self.uuid
, ie the value of your model's uuid attribute.
However your loop is only setting a local variable called uuid, so the attribute is always nil (since you only assign a value to it once the loop has completed), and your existance check does nothing. This unrelated to the fact that the method is stubbed (other than the fact that the stub is allowing you to test the case where a duplicate uuid was returned)
You could either pass the value to your uuid_exists?
method as you suggest or do away with the local variable uuid altogether.
Upvotes: 2