Hi, I'm Tomek.

I'm a software engineer and I solve problems by (not) writing Clojure. You can find me as @_tomekw on Twitter, @tomekw on Mastodon, or @tomekw on Github. I write (mostly) about programming.

Unit testing ActionCable channels with RSpec

Jun 18, 2018

Rails 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

Do you like what to see here?

Consider subscribing to my newsletter. Get helpful guides, articles, and projects announcements. Low traffic, no spam.

Or subscribe to my Atom feed.