New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support nested time travel helpers #24690
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,48 @@ | ||
module ActiveSupport | ||
module Testing | ||
class SimpleStubs # :nodoc: | ||
Stub = Struct.new(:object, :method_name, :original_method) | ||
Stub = Struct.new(:object, :method_name, :original_method, :return_value) | ||
|
||
def initialize | ||
@stubs = {} | ||
@stubs = Hash.new { |h,k| h[k] = [] } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would flipping the relationship clarify the intent? Found having stubs be per method and then the stub levels on each method hard to grasp and read. |
||
end | ||
|
||
def stub_object(object, method_name, return_value) | ||
key = [object.object_id, method_name] | ||
|
||
if stub = @stubs[key] | ||
unstub_object(stub) | ||
if @stubs[key].any? | ||
stub = @stubs[key].last | ||
unstub_object(stub) if stub | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could simplify to: if stub = @stubs[key].last
unstub_object(stub) if stub
end |
||
end | ||
|
||
new_name = "__simple_stub__#{method_name}" | ||
|
||
@stubs[key] = Stub.new(object, method_name, new_name) | ||
@stubs[key].push(Stub.new(object, method_name, new_name, return_value)) | ||
|
||
object.singleton_class.send :alias_method, new_name, method_name | ||
object.define_singleton_method(method_name) { return_value } | ||
end | ||
|
||
def unstub_all! | ||
@stubs.each_value do |stub| | ||
unstub_object(stub) | ||
@stubs.each_value do |stubs| | ||
stub = stubs.first | ||
unstub_object(stub) if stub | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be a good case for @stubs.each_value do |(stub_masking_original_methods, *)| |
||
@stubs.clear | ||
end | ||
|
||
def unstub_to_previous_stub_state(object, method_name, return_value) | ||
key = [object.object_id, method_name] | ||
|
||
if @stubs[key].any? | ||
current_stub = @stubs[key].pop | ||
|
||
if stub = @stubs[key].last | ||
stub_object(stub.object, stub.method_name, stub.return_value) | ||
else | ||
unstub_object(current_stub) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could do with a better method name 😁 Since the stubs per method is essentially turned into a stack we could go with Here's my take: def pop_stub(object, method_name)
key = [ object.object_id, method_name ]
currently_stubbed_to = @stubs[key].pop
if stub_next_in_stack = @stubs[key].last
stub_object(object, method_name, stub_next_in_stack.return_value)
else
unstub_object(currently_stubbed_to)
end
end Renamed some variables because I found them hard to keep track of. |
||
end | ||
@stubs = {} | ||
end | ||
|
||
private | ||
|
@@ -98,16 +114,16 @@ def travel_to(date_or_time) | |
else | ||
now = date_or_time.to_time.change(usec: 0) | ||
end | ||
to_date_value = now.to_date | ||
to_datetime_value = now.to_datetime | ||
|
||
simple_stubs.stub_object(Time, :now, now) | ||
simple_stubs.stub_object(Date, :today, now.to_date) | ||
simple_stubs.stub_object(DateTime, :now, now.to_datetime) | ||
stub_unstub_date_time_objects(:stub_object, now, to_date_value, to_datetime_value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With |
||
|
||
if block_given? | ||
begin | ||
yield | ||
ensure | ||
travel_back | ||
stub_unstub_date_time_objects(:unstub_to_previous_stub_state, now, to_date_value, to_datetime_value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With simple_stubs.pop_stub(Time, :now)
simple_stubs.pop_stub(Date, :today)
simple_stubs.pop_stub(DateTime, :now) And we could kill There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should lose the travel_back call. So this code should be inside travel_back. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @diabolo travel_back is travelling all the way back. Maybe we can rename it. |
||
end | ||
end | ||
end | ||
|
@@ -129,6 +145,12 @@ def travel_back | |
def simple_stubs | ||
@simple_stubs ||= SimpleStubs.new | ||
end | ||
|
||
def stub_unstub_date_time_objects(method, now, to_date_value, to_datetime_value) | ||
simple_stubs.public_send(method, Time, :now, now) | ||
simple_stubs.public_send(method, Date, :today, to_date_value) | ||
simple_stubs.public_send(method, DateTime, :now, to_datetime_value) | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think we could also get more mileage out of pushing some behavior into the Stub class.
Here's a spike:
Which with (in SimpleStubs):
Would be used like:
I think capturing the return values in an array should let us avoid having to keep redefining the methods every stack push 😁