Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/palkan/action-cable-testing
Action Cable testing utils
https://github.com/palkan/action-cable-testing
actioncable minitest rails rspec testing
Last synced: about 5 hours ago
JSON representation
Action Cable testing utils
- Host: GitHub
- URL: https://github.com/palkan/action-cable-testing
- Owner: palkan
- License: mit
- Created: 2017-10-22T12:34:33.000Z (about 7 years ago)
- Default Branch: master
- Last Pushed: 2021-01-18T16:12:46.000Z (almost 4 years ago)
- Last Synced: 2024-12-25T00:05:16.774Z (7 days ago)
- Topics: actioncable, minitest, rails, rspec, testing
- Language: Ruby
- Size: 168 KB
- Stars: 213
- Watchers: 4
- Forks: 17
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
Awesome Lists containing this project
README
[![Gem Version](https://badge.fury.io/rb/action-cable-testing.svg)](https://rubygems.org/gems/action-cable-testing)
![Build](https://github.com/palkan/action-cable-testing/workflows/Build/badge.svg)
[![Build Status](https://travis-ci.org/palkan/action-cable-testing.svg?branch=master)](https://travis-ci.org/palkan/action-cable-testing)[![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/action-cable-testing)# Action Cable Testing
This gem provides missing testing utils for [Action Cable][].
**NOTE:** this gem [has](https://github.com/rails/rails/pull/33659) [been](https://github.com/rails/rails/pull/33969) [merged](https://github.com/rails/rails/pull/34845) into Rails 6.0 and [into RSpec 4](https://github.com/rspec/rspec-rails/pull/2113).
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:
```ruby
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:
```ruby
# 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::TestHelperdef 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**:```ruby
# 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::Rails6class ChatRelayJobTest < ActiveJob::TestCase
include ActionCable::TestHelpertest "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 https://github.com/rails/rails/pull/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:
```ruby
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)
enddef test_subscribed_without_room_number
subscribeassert subscription.confirmed?
# Asserts that no streams was started
# (e.g., we want to subscribe later by performing an action)
assert_no_streams
enddef 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:
```ruby
def test_perform_speak
subscribe room_number: 1perform :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:
```ruby
class ChatChannelTest < ActionCable::Channel::TestCase
include ActionCable::TestHelperdef 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:```ruby
class ChatChannelTest < ActionCable::Channel::TestCase
def setup
@room = Room.find 1stub_connection(user: users[:john])
subscribe room_number: room.id
enddef 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:
```ruby
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
enddef 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:
```ruby
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"
enddef 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](https://github.com/rspec/rspec-rails) installed.
Second, add this to your `"rails_helper.rb"` after requiring `environment.rb`:
```ruby
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`:
```yml
# config/cable.yml
test:
adapter: test
```And then use these matchers, for example:
```ruby
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:
```ruby
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:
```ruby
# spec/channels/chat_channel_spec.rbrequire "rails_helper"
RSpec.describe ChatChannel, type: :channel do
before do
# initialize connection with identifiers
stub_connection user_id: user.id
endit "subscribes without streams when no room id" do
subscribeexpect(subscription).to be_confirmed
expect(subscription).not_to have_streams
endit "rejects when room id is invalid" do
subscribe(room_id: -1)expect(subscription).to be_rejected
endit "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:
```ruby
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"
endit "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:
```ruby
# 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"`:
```ruby
# 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:
```sh
# 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](http://opensource.org/licenses/MIT).
[Action Cable]: http://guides.rubyonrails.org/action_cable_overview.html