Unit testing ActionCable channels with RSpec
Jun 18, 2018Rails 5.1 doesn’t have official support for testing ActionCable channels yet. Here is the minimal example how to unit test a single channel action:
# app/channels/hello_channel.rb
class HelloChannel < ActionCable::Channel::Base
def say_hello(data)
times_to_say_hello = data.fetch("times_to_say_hello")
hello = "Hello, #{current_profile.name}!"
times_to_say_hello.times do
ActionCable.server.broadcast(current_profile.id, hello)
end
end
end
# spec/channels/hello_channel_spec.rb
require "spec_helper"
# This is the minimal ActionCable connection stub to make the test pass
class TestConnection
attr_reader :identifiers, :logger
def initialize(identifiers_hash = {})
@identifiers = identifiers_hash.keys
@logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(StringIO.new))
# This is an equivalent of providing `identified_by :identifier_key` in ActionCable::Connection::Base subclass
identifiers_hash.each do |identifier, value|
define_singleton_method(identifier) do
value
end
end
end
end
RSpec.describe HelloChannel do
subject(:channel) { described_class.new(connection, {}) }
let(:current_profile) { double(id: "1", name: "Bob") }
# Connection is `identified_by :current_profile`
let(:connection) { TestConnection.new(current_profile: current_profile) }
let(:action_cable) { ActionCable.server }
# ActionCable dispatches actions by the `action` attribute.
# In this test we assume the payload was successfully parsed (it could be a JSON payload, for example).
let(:data) do
{
"action" => "test_action",
"times_to_say_hello" => 3
}
end
it "broadcasts 'Hello, Bob!' 3 times" do
expect(action_cable).to receive(:broadcast).with("1", "Hello, Bob!").exactly(3).times
channel.perform_action(data)
end
end