• Stars
    star
    209
  • Rank 181,503 (Top 4 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 6 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Action Cable testing utils

Gem Version Build Build StatusYard Docs

Action Cable Testing

This gem provides missing testing utils for Action Cable.

NOTE: this gem has been merged into Rails 6.0 and into RSpec 4.

If you're using Minitest – you don't need this gem anymore.

If you're using RSpec < 4, you still can use this gem to write Action Cable specs even for Rails 6.

Installation

For Rails < 6.0 ONLY:

Add this line to your application's Gemfile:

gem 'action-cable-testing'

And then execute:

$ bundle

For Usage with Rspec (any version of Rails, including 6+):

add to spec/rails_helper.rb

RSpec.configure do |config|
 //more rspec configs...
 config.include ActionCable::TestHelper
end

(note in older versions of Rails you will make Rspec config changes in spec_helper.rb)

Usage

Test Adapter and Broadcasting

We add ActionCable::SubscriptionAdapter::Test (very similar Active Job and Action Mailer tests adapters) and ActionCable::TestCase with a couple of matchers to track broadcasting messages in our tests:

# Using ActionCable::TestCase
class MyCableTest < ActionCable::TestCase
  def test_broadcasts
    # Check the number of messages broadcasted to the stream
    assert_broadcasts 'messages', 0
    ActionCable.server.broadcast 'messages', { text: 'hello' }
    assert_broadcasts 'messages', 1

    # Check the number of messages broadcasted to the stream within a block
    assert_broadcasts('messages', 1) do
      ActionCable.server.broadcast 'messages', { text: 'hello' }
    end

    # Check that no broadcasts has been made
    assert_no_broadcasts('messages') do
      ActionCable.server.broadcast 'another_stream', { text: 'hello' }
    end
  end
end

# Or including ActionCable::TestHelper
class ExampleTest < ActionDispatch::IntegrationTest
  include ActionCable::TestHelper

  def test_broadcasts
    room = rooms(:office)

    assert_broadcast_on("messages:#{room.id}", text: 'Hello!') do
      post "/say/#{room.id}", xhr: true, params: { message: 'Hello!' }
    end
  end
end

If you want to test the broadcasting made with Channel.broadcast_to, you should use Channel.broadcasting_for* to generate an underlying stream name and use Rails 6 compatibility refinement:

# app/jobs/chat_relay_job.rb
class ChatRelayJob < ApplicationJob
  def perform_later(room, message)
    ChatChannel.broadcast_to room, text: message
  end
end


# test/jobs/chat_relay_job_test.rb
require "test_helper"

# Activate Rails 6 compatible API (for `broadcasting_for`)
using ActionCable::Testing::Rails6

class ChatRelayJobTest < ActiveJob::TestCase
  include ActionCable::TestHelper

  test "broadcast message to room" do
    room = rooms(:all)

    assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do
      ChatRelayJob.perform_now(room, "Hi!")
    end
  end
end

* NOTE: in Rails 6.0 you should use .broadcasting_for, but it's not backward compatible and we cannot use it in Rails 5.x. See rails/rails#35021. Note also, that this feature hasn't been released in Rails 6.0.0.beta1, so you still need the refinement.

Channels Testing

Channels 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_has_stream "chat_1"

    # Asserts that the channel subscribes connection to a stream created with `stream_for`
    assert_has_stream_for Room.find(1)
  end

  def test_subscribed_without_room_number
    subscribe

    assert subscription.confirmed?
    # Asserts that no streams was started
    # (e.g., we want to subscribe later by performing an action)
    assert_no_streams
  end

  def test_does_not_subscribe_with_invalid_room_number
    subscribe room_number: -1

    # 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!"

  # `transmissions` stores messages sent directly to the channel (i.e. with `transmit` method)
  assert_equal "Hello, Rails!", transmissions.last["text"]
end

You can set up your connection identifiers:

class ChatChannelTest < ActionCable::Channel::TestCase
  include ActionCable::TestHelper

  def test_identifiers
    stub_connection(user: users[:john])

    subscribe room_number: 1

    assert_broadcast_on("messages_1", text: "I'm here!", from: "John") do
      perform :speak, message: "I'm here!"
    end
  end
end

When broadcasting to an object:

class ChatChannelTest < ActionCable::Channel::TestCase
  def setup
    @room = Room.find 1

    stub_connection(user: users[:john])
    subscribe room_number: room.id
  end

  def test_broadcasting
    assert_broadcasts(@room, 1) do
      perform :speak, message: "I'm here!"
    end
  end

  # or

  def test_broadcasted_data
    assert_broadcast_on(@room, text: "I'm here!", from: "John") do
      perform :speak, message: "I'm here!"
    end
  end
end

Connection Testing

Connection unit tests are written as follows:

  1. First, one uses the connect method to simulate connection.
  2. Then, one asserts whether the current state is as expected (e.g. identifiers).

For example:

module ApplicationCable
  class ConnectionTest < ActionCable::Connection::TestCase
    def test_connects_with_cookies
      cookies.signed[:user_id] = users[:john].id

      # Simulate a connection
      connect

      # Asserts that the connection identifier is correct
      assert_equal "John", connection.user.name
    end

    def test_does_not_connect_without_user
      assert_reject_connection do
        connect
      end
    end
  end
end

You can also provide additional information about underlying HTTP request:

def test_connect_with_headers_and_query_string
  connect "/cable?user_id=1", headers: { "X-API-TOKEN" => 'secret-my' }

  assert_equal connection.user_id, "1"
end

def test_connect_with_session
  connect "/cable", session: { users[:john].id }

  assert_equal connection.user_id, "1"
end

RSpec Usage

First, you need to have rspec-rails installed.

Second, add this to your "rails_helper.rb" after requiring environment.rb:

require "action_cable/testing/rspec"

To use have_broadcasted_to / broadcast_to matchers anywhere in your specs, set your adapter to test in cable.yml:

# config/cable.yml
test:
  adapter: test

And then use these matchers, for example:

RSpec.describe CommentsController do
  describe "POST #create" do
    expect { post :create, comment: { text: 'Cool!' } }.to
      have_broadcasted_to("comments").with(text: 'Cool!')
  end
end

Or when broadcasting to an object:

RSpec.describe CommentsController do
  describe "POST #create" do
    let(:the_post) { create :post }

    expect { post :create, comment: { text: 'Cool!', post_id: the_post.id } }.to
      have_broadcasted_to(the_post).from_channel(PostChannel).with(text: 'Cool!')
  end
end

You can also unit-test your channels:

# spec/channels/chat_channel_spec.rb

require "rails_helper"

RSpec.describe ChatChannel, type: :channel do
  before do
    # initialize connection with identifiers
    stub_connection user_id: user.id
  end

  it "subscribes without streams when no room id" do
    subscribe

    expect(subscription).to be_confirmed
    expect(subscription).not_to have_streams
  end

  it "rejects when room id is invalid" do
    subscribe(room_id: -1)

    expect(subscription).to be_rejected
  end

  it "subscribes to a stream when room id is provided" do
    subscribe(room_id: 42)

    expect(subscription).to be_confirmed

    # check particular stream by name
    expect(subscription).to have_stream_from("chat_42")

    # or directly by model if you create streams with `stream_for`
    expect(subscription).to have_stream_for(Room.find(42))
  end
end

And, of course, connections:

require "rails_helper"

RSpec.describe ApplicationCable::Connection, type: :channel do
  it "successfully connects" do
    connect "/cable", headers: { "X-USER-ID" => "325" }
    expect(connection.user_id).to eq "325"
  end

  it "rejects connection" do
    expect { connect "/cable" }.to have_rejected_connection
  end
end

NOTE: for connections testing you must use type: :channel too.

Shared contexts to switch between adapters

NOTE: this feature is gem-only and hasn't been migrated to RSpec 4. You can still use the gem for that by adding require "rspec/rails/shared_contexts/action_cable" to your rspec_helper.rb.

Sometimes you may want to use real Action Cable adapter instead of the test one (for example, in Capybara-like tests).

We provide shared contexts to do that:

# Use async adapter for this example group only
RSpec.describe "cable case", action_cable: :async do
 # ...

  context "inline cable", action_cable: :inline do
    # ...
  end

  # or test adapter
  context "test cable", action_cable: :test do
    # ...
  end

  # you can also include contexts by names
  context "by name" do
    include "action_cable:async"
    # ...
  end
end

We also provide an integration for feature specs (having type: :feature). Just add require "action_cable/testing/rspec/features":

# rails_helper.rb
require "action_cable/testing/rspec"
require "action_cable/testing/rspec/features"

# spec/features/my_feature_spec.rb
feature "Cables!" do
  # here we have "action_cable:async" context included automatically!
end

For more RSpec documentation see https://relishapp.com/palkan/action-cable-testing/docs.

Generators

This gem also provides Rails generators:

# Generate a channel test case for ChatChannel
rails generate test_unit:channel chat

# or for RSpec
rails generate rspec:channel chat

Development

After checking out the repo, run bundle install to install dependencies. Then, run bundle exec rake to run the tests.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/action-cable-testing.

License

The gem is available as open source under the terms of the MIT License.

More Repositories

1

logidze

Database changes log for Rails
Ruby
1,555
star
2

action_policy

Authorization framework for Ruby/Rails applications
Ruby
1,333
star
3

isolator

Detect non-atomic interactions within DB transactions
Ruby
814
star
4

anyway_config

Configuration library for Ruby gems and applications
Ruby
719
star
5

active_delivery

Ruby framework for keeping all types of notifications (mailers, push notifications, whatever) in one place
Ruby
585
star
6

n_plus_one_control

RSpec and Minitest matchers to prevent N+1 queries problem
Ruby
543
star
7

store_attribute

ActiveRecord extension which adds typecasting to store accessors
Ruby
344
star
8

view_component-contrib

A collection of extension and developer tools for ViewComponent
Ruby
318
star
9

litecable

Lightweight Action Cable implementation (Rails-free)
Ruby
285
star
10

acli

Action Cable command-line client
Ruby
222
star
11

rubanok

Parameters-based transformation DSL
Ruby
200
star
12

active_event_store

Rails Event Store in a more Rails way
Ruby
167
star
13

action_policy-graphql

Action Policy integration for GraphQL
Ruby
149
star
14

engems

Rails component-based architecture on top of engines and gems (showroom)
Ruby
136
star
15

influxer

InfluxDB ActiveRecord-style
Ruby
118
star
16

abstract_notifier

ActionMailer-like interface for any type of notifications
Ruby
116
star
17

wsdirector

All the world's a server, and all the men and women merely clients
Ruby
99
star
18

pgrel

ActiveRecord extension for querying hstore and jsonb
Ruby
93
star
19

gem-check

GemCheck: Writing Better Ruby Gems Checklist
CSS
93
star
20

turbo-music-drive

Exploring Turbo future features while building a music library app
Ruby
89
star
21

rbytes

Ruby Bytes helps you build, deploy and install Ruby and Rails application templates
Ruby
65
star
22

faqueue

Researching background jobs fairness
Ruby
63
star
23

downstream

Straightforward way to implement communication between Rails Engines using the Publish-Subscribe pattern.
Ruby
47
star
24

influx_udp

Erlang InfluxDB UDP writer
Erlang
31
star
25

newgem

Custom script to generate new gems
Ruby
30
star
26

ruby-dip

Docker-based development environment for hacking Ruby MRI
Dockerfile
30
star
27

turbo-view-transitions

View Transitions API for Turbo
TypeScript
28
star
28

erlgrpc

GRPC client for Erlang
Erlang
25
star
29

as3_p2plocal

as3 lib for local p2p connections (serverless rtmfp)
ActionScript
25
star
30

rails-intest-views

Generate view templates dynamically in Rails tests
Ruby
20
star
31

sharelatex-vagrant-ansible

Vagrant + Ansible configuration for ShareLatex
Shell
12
star
32

docsify-namespaced

Docsify plugin to work with namespaces
JavaScript
11
star
33

docs-example

Playground for dealing with documentation engines
7
star
34

ruby-russia-2020

RubyRussia 2020 "Frontendless Rails" workshop demo app
Ruby
6
star
35

engine-cable-app

Experimenting with Action Cable and engines
Ruby
6
star
36

palkan

It's me
4
star
37

ruby-compatibility-examples

Collections of reproduction cases for TruffleRuby vs. MRI (in)compatibility
Ruby
3
star
38

erffmpeg

Erlang wrapper for some ffmpeg
C
3
star
39

th-dummy

TH Dummy
Ruby
2
star
40

ulitos

Erlang utils modules
Erlang
2
star
41

meetings

Good old Teachbase Meetings client
ActionScript
2
star
42

macos-setup

Shell
1
star
43

bitrix-orm

Bitrix kinda ORM for IBlockElements and CUser.
PHP
1
star
44

adventofcode2018

https://adventofcode.com
Rust
1
star
45

tb_utils

ActionScript 3 library
ActionScript
1
star
46

rebar_templates

Custom rebar templates
Erlang
1
star