forked from palkan/action-cable-testing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_case.rb
229 lines (202 loc) · 7.25 KB
/
test_case.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# frozen_string_literal: true
require "active_support"
require "active_support/test_case"
require "active_support/core_ext/hash/indifferent_access"
require "json"
module ActionCable
module Channel
class NonInferrableChannelError < ::StandardError
def initialize(name)
super "Unable to determine the channel to test from #{name}. " +
"You'll need to specify it using tests YourChannel in your " +
"test case definition."
end
end
# Stub `stream_from` to track streams for the channel.
# Add public aliases for `subscription_confirmation_sent?` and
# `subscription_rejected?`.
module ChannelStub
def confirmed?
subscription_confirmation_sent?
end
def rejected?
subscription_rejected?
end
def stream_from(broadcasting, *)
streams << broadcasting
end
def stop_all_streams
@_streams = []
end
def streams
@_streams ||= []
end
end
class ConnectionStub
attr_reader :transmissions, :identifiers, :subscriptions, :logger
def initialize(identifiers = {})
@transmissions = []
identifiers.each do |identifier, val|
define_singleton_method(identifier) { val }
end
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
@identifiers = identifiers.keys
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
end
def transmit(cable_message)
transmissions << cable_message.with_indifferent_access
end
end
# Superclass for Action Cable channel functional tests.
#
# == Basic example
#
# Functional tests are written as follows:
# 1. First, one uses the +subscribe+ method to simulate subscription creation.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# transmitted messages, subscribed streams, etc.
#
# For example:
#
# class ChatChannelTest < ActionCable::Channel::TestCase
# def test_subscribed_with_room_number
# # Simulate a subscription creation
# subscribe room_number: 1
#
# # Asserts that the subscription was successfully created
# assert subscription.confirmed?
#
# # Asserts that the channel subscribes connection to a stream
# assert_equal "chat_1", streams.last
# end
#
# def test_does_not_subscribe_without_room_number
# subscribe
#
# # Asserts that the subscription was rejected
# assert subscription.rejected?
# end
# end
#
# You can also perform actions:
# def test_perform_speak
# subscribe room_number: 1
#
# perform :speak, message: "Hello, Rails!"
#
# assert_equal "Hello, Rails!", transmissions.last["text"]
# end
#
# == Special methods
#
# ActionCable::Channel::TestCase will also automatically provide the following instance
# methods for use in the tests:
#
# <b>connection</b>::
# An ActionCable::Channel::ConnectionStub, representing the current HTTP connection.
# <b>subscription</b>::
# An instance of the current channel, created when you call `subscribe`.
# <b>transmissions</b>::
# A list of all messages that have been transmitted into the channel.
# <b>streams</b>::
# A list of all created streams subscriptions (as identifiers) for the subscription.
#
#
# == Channel is automatically inferred
#
# ActionCable::Channel::TestCase will automatically infer the channel under test
# from the test class name. If the channel cannot be inferred from the test
# class name, you can explicitly set it with +tests+.
#
# class SpecialEdgeCaseChannelTest < ActionCable::Channel::TestCase
# tests SpecialChannel
# end
#
# == Specifying connection identifiers
#
# You need to set up your connection manually to privide values for the identifiers.
# To do this just use:
#
# stub_connection(user: users[:john])
class TestCase < ActiveSupport::TestCase
module Behavior
extend ActiveSupport::Concern
include ActiveSupport::Testing::ConstantLookup
include ActionCable::TestHelper
include ActionCable::Connection::TestCase::Behavior
CHANNEL_IDENTIFIER = "test_stub"
included do
class_attribute :_channel_class
attr_reader :subscription
delegate :streams, to: :subscription
ActiveSupport.run_load_hooks(:action_cable_channel_test_case, self)
end
module ClassMethods
def tests(channel)
case channel
when String, Symbol
self._channel_class = channel.to_s.camelize.constantize
when Module
self._channel_class = channel
else
raise NonInferrableChannelError.new(channel)
end
end
def channel_class
if channel = self._channel_class
channel
else
tests determine_default_channel(name)
end
end
def determine_default_channel(name)
channel = determine_constant_from_test_name(name) do |constant|
Class === constant && constant < ActionCable::Channel::Base
end
raise NonInferrableChannelError.new(name) if channel.nil?
channel
end
end
# Setup test connection with the specified identifiers:
#
# class ApplicationCable < ActionCable::Connection::Base
# identified_by :user, :token
# end
#
# stub_connection(user: users[:john], token: 'my-secret-token')
def stub_connection(identifiers = {})
@connection = ConnectionStub.new(identifiers)
end
# Subsribe to the channel under test. Optionally pass subscription parameters as a Hash.
def subscribe(params = {})
@connection ||= stub_connection
# NOTE: Rails < 5.0.1 calls subscribe_to_channel during #initialize.
# We have to stub before it
@subscription = self.class.channel_class.allocate
@subscription.singleton_class.include(ChannelStub)
@subscription.send(:initialize, connection, CHANNEL_IDENTIFIER, params.with_indifferent_access)
# Call subscribe_to_channel if it's public (Rails 5.0.1+)
@subscription.subscribe_to_channel if ActionCable.gem_version >= Gem::Version.new("5.0.1")
@subscription
end
# Perform action on a channel.
#
# NOTE: Must be subscribed.
def perform(action, data = {})
check_subscribed!
subscription.perform_action(data.stringify_keys.merge("action" => action.to_s))
end
# Returns messages transmitted into channel
def transmissions
# Return only directly sent message (via #transmit)
connection.transmissions.map { |data| data["message"] }.compact
end
private
def check_subscribed!
raise "Must be subscribed!" if subscription.nil? || subscription.rejected?
end
end
include Behavior
end
end
end