Skip to content

Commit fe99be1

Browse files
joeljunstrompalkan
authored andcommitted
Add advance_time helper to drive periodic timers in tests
Lets tests assert on `periodically` callback behavior without a real clock.
1 parent 4ae9bad commit fe99be1

3 files changed

Lines changed: 87 additions & 3 deletions

File tree

lib/action_cable/channel/test_case.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,15 @@ def perform(action, data = {})
238238
subscription.perform_action(data.stringify_keys.merge("action" => action.to_s))
239239
end
240240

241+
# Advance the virtual clock used by `periodically` timers.
242+
#
243+
# subscribe
244+
# advance_time 5.seconds
245+
# assert_equal 1, subscription.tick_count
246+
def advance_time(seconds)
247+
testserver.advance_time(seconds)
248+
end
249+
241250
# Returns messages transmitted into channel
242251
def transmissions
243252
# Return only directly sent message (via #transmit)

lib/action_cable/connection/test_case.rb

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,44 @@ def transmit(data)
100100
def close
101101
@closed = true
102102
end
103+
104+
def perform_work(receiver, method_name, *args)
105+
receiver.public_send(method_name, *args)
106+
end
103107
end
104108

105109
class TestTimer
106-
def shutdown; end
110+
attr_reader :interval
111+
112+
def initialize(interval, &block)
113+
@interval = interval
114+
@block = block
115+
@elapsed = 0
116+
@shutdown = false
117+
end
118+
119+
def shutdown
120+
@shutdown = true
121+
end
122+
123+
def advance(seconds)
124+
return if @shutdown
125+
@elapsed += seconds
126+
while @elapsed >= @interval
127+
@elapsed -= @interval
128+
@block&.call
129+
end
130+
end
107131
end
108132

109133
# TestServer provides test pub/sub and executor implementations
110134
class TestServer
111-
attr_reader :streams, :config
135+
attr_reader :streams, :config, :timers
112136

113137
def initialize(server)
114138
@streams = Hash.new { |h, k| h[k] = [] }
115139
@config = server.config
140+
@timers = []
116141
end
117142

118143
alias_method :pubsub, :itself
@@ -122,7 +147,14 @@ def initialize(server)
122147

123148
# Inline async calls
124149
def post(&work) = work.call
125-
def timer(_every) = TestTimer.new
150+
151+
def timer(every, &block)
152+
TestTimer.new(every, &block).tap { |t| @timers << t }
153+
end
154+
155+
def advance_time(seconds)
156+
@timers.each { |timer| timer.advance(seconds) }
157+
end
126158

127159
#== Pub/sub interface ==
128160
def subscribe(stream, callback, success_callback = nil)

test/channel/test_case_test.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,49 @@ def test_reject_does_not_crash_when_periodic_timer_is_registered
115115
end
116116
end
117117

118+
class PeriodicCounterChannel < ActionCable::Channel::Base
119+
periodically :tick, every: 5
120+
121+
attr_reader :tick_count
122+
123+
def subscribed
124+
@tick_count = 0
125+
end
126+
127+
private
128+
def tick
129+
@tick_count += 1
130+
end
131+
end
132+
133+
class PeriodicCounterChannelTest < ActionCable::Channel::TestCase
134+
tests PeriodicCounterChannel
135+
136+
def test_advance_time_fires_periodic_callback_when_interval_is_reached
137+
subscribe
138+
assert_equal 0, subscription.tick_count
139+
140+
advance_time 4
141+
assert_equal 0, subscription.tick_count
142+
143+
advance_time 1
144+
assert_equal 1, subscription.tick_count
145+
146+
advance_time 12
147+
assert_equal 3, subscription.tick_count
148+
end
149+
150+
def test_shutdown_timer_stops_firing_after_unsubscribe
151+
subscribe
152+
advance_time 5
153+
assert_equal 1, subscription.tick_count
154+
155+
unsubscribe
156+
advance_time 100
157+
assert_equal 1, subscription.tick_count
158+
end
159+
end
160+
118161
class StreamsTestChannel < ActionCable::Channel::Base
119162
def subscribed
120163
stream_from "test_#{params[:id] || 0}"

0 commit comments

Comments
 (0)