Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Action Cable testing #2113

Merged
merged 3 commits into from May 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions Rakefile
Expand Up @@ -50,6 +50,10 @@ Cucumber::Rake::Task.new(:cucumber) do |t|
tags << "~@system_test"
end

if version.to_f < 6.0
tags << "~@rails_post_6"
end

cucumber_flag = tags.map { |tag| "--tag #{tag}" }

t.cucumber_opts = cucumber_flag
Expand Down
7 changes: 7 additions & 0 deletions example_app_generator/generate_stuff.rb
Expand Up @@ -111,6 +111,13 @@ def using_source_path(path)
rescue LoadError
end

begin
require 'action_cable'
require 'action_cable/test_helper'
generate('channel chat')
rescue LoadError
JonRowe marked this conversation as resolved.
Show resolved Hide resolved
end

file "app/views/things/custom_action.html.erb",
"This is a template for a custom action.",
:force => true
Expand Down
216 changes: 216 additions & 0 deletions features/channel_specs/channel_spec.feature
@@ -0,0 +1,216 @@
@rails_post_6
Feature: channel spec
benoittgt marked this conversation as resolved.
Show resolved Hide resolved

Channel specs are marked by `:type => :channel` or if you have set
`config.infer_spec_type_from_file_location!` by placing them in `spec/channels`.

A channel spec is a thin wrapper for an ActionCable::Channel::TestCase, and includes all
of the behavior and assertions that it provides, in addition to RSpec's own
behavior and expectations.

It also includes helpers from ActionCable::Connection::TestCase to make it possible to
test connection behavior.

Background:
Given action cable testing is available
And a file named "app/channels/chat_channel.rb" with:
"""ruby
class ChatChannel < ApplicationCable::Channel
def subscribed
reject unless params[:room_id].present?
end

def speak(data)
ActionCable.server.broadcast(
"chat_#{params[:room_id]}", text: data['message']
)
end

def echo(data)
data.delete("action")
transmit data
end
end
"""

Scenario: simple passing example
Given a file named "spec/channels/chat_channel_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe ChatChannel, :type => :channel do
it "successfully subscribes" do
subscribe room_id: 42
expect(subscription).to be_confirmed
end
end
"""
When I run `rspec spec/channels/chat_channel_spec.rb`
Then the example should pass

Scenario: verifying that subscription is rejected
Given a file named "spec/channels/chat_channel_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe ChatChannel, :type => :channel do
it "rejects subscription" do
subscribe room_id: nil
expect(subscription).to be_rejected
end
end
"""
When I run `rspec spec/channels/chat_channel_spec.rb`
Then the example should pass

Scenario: performing actions and checking transmissions
Given a file named "spec/channels/chat_channel_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe ChatChannel, :type => :channel do
it "successfully subscribes" do
subscribe room_id: 42

perform :echo, foo: 'bar'
expect(transmissions.last).to eq('foo' => 'bar')
end
end
"""
When I run `rspec spec/channels/chat_channel_spec.rb`
Then the example should pass

Scenario: successful connection with url params
Given a file named "app/channels/application_cable/connection.rb" with:
"""ruby
class ApplicationCable::Connection < ActionCable::Connection::Base
identified_by :user_id

def connect
self.user_id = request.params[:user_id]
reject_unauthorized_connection unless user_id.present?
end
end
"""
And a file named "spec/channels/connection_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe ApplicationCable::Connection, :type => :channel do
it "successfully connects" do
connect "/cable?user_id=323"
expect(connection.user_id).to eq "323"
end
end
"""
When I run `rspec spec/channels/connection_spec.rb`
Then the example should pass

Scenario: successful connection with cookies
Given a file named "app/channels/application_cable/connection.rb" with:
"""ruby
class ApplicationCable::Connection < ActionCable::Connection::Base
identified_by :user_id

def connect
self.user_id = cookies.signed[:user_id]
reject_unauthorized_connection unless user_id.present?
end
end
"""
And a file named "spec/channels/connection_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe ApplicationCable::Connection, :type => :channel do
it "successfully connects" do
cookies.signed[:user_id] = "324"

connect "/cable"
expect(connection.user_id).to eq "324"
end
end
"""
When I run `rspec spec/channels/connection_spec.rb`
Then the example should pass

Scenario: successful connection with headers
Given a file named "app/channels/application_cable/connection.rb" with:
"""ruby
class ApplicationCable::Connection < ActionCable::Connection::Base
identified_by :user_id

def connect
self.user_id = request.headers["x-user-id"]
reject_unauthorized_connection unless user_id.present?
end
end
"""
And a file named "spec/channels/connection_spec.rb" with:
"""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"
end
end
"""
When I run `rspec spec/channels/connection_spec.rb`
Then the example should pass

Scenario: rejected connection
Given a file named "app/channels/application_cable/connection.rb" with:
"""ruby
class ApplicationCable::Connection < ActionCable::Connection::Base
identified_by :user_id

def connect
self.user_id = request.params[:user_id]
reject_unauthorized_connection unless user_id.present?
end
end
"""
And a file named "spec/channels/connection_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe ApplicationCable::Connection, :type => :channel do
it "rejects connection" do
expect { connect "/cable?user_id=" }.to have_rejected_connection
end
end
"""
When I run `rspec spec/channels/connection_spec.rb`
Then the example should pass

Scenario: disconnect connection
Given a file named "app/channels/application_cable/connection.rb" with:
"""ruby
class ApplicationCable::Connection < ActionCable::Connection::Base
identified_by :user_id

def connect
self.user_id = request.params[:user_id]
reject_unauthorized_connection unless user_id.present?
end

def disconnect
$stdout.puts "User #{user_id} disconnected"
end
end
"""
And a file named "spec/channels/connection_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe ApplicationCable::Connection, :type => :channel do
it "disconnects" do
connect "/cable?user_id=42"
expect { disconnect }.to output(/User 42 disconnected/).to_stdout
end
end
"""
When I run `rspec spec/channels/connection_spec.rb`
Then the example should pass
151 changes: 151 additions & 0 deletions features/matchers/have_broadcasted_matcher.feature
@@ -0,0 +1,151 @@
@rails_post_6
Feature: have_broadcasted matcher
benoittgt marked this conversation as resolved.
Show resolved Hide resolved
benoittgt marked this conversation as resolved.
Show resolved Hide resolved

The `have_broadcasted_to` (also aliased as `broadcast_to`) matcher is used to check if a message has been broadcasted to a given stream.

Background:
Given action cable testing is available

Scenario: Checking stream name
Given a file named "spec/models/broadcaster_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe "broadcasting" do
it "matches with stream name" do
expect {
ActionCable.server.broadcast(
"notifications", text: 'Hello!'
)
}.to have_broadcasted_to("notifications")
end
end
"""
When I run `rspec spec/models/broadcaster_spec.rb`
Then the examples should all pass

Scenario: Checking passed message to stream
Given a file named "spec/models/broadcaster_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe "broadcasting" do
it "matches with message" do
expect {
ActionCable.server.broadcast(
"notifications", text: 'Hello!'
)
}.to have_broadcasted_to("notifications").with(text: 'Hello!')
end
end
"""
When I run `rspec spec/models/broadcaster_spec.rb`
Then the examples should all pass

Scenario: Checking that message passed to stream matches
Given a file named "spec/models/broadcaster_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe "broadcasting" do
it "matches with message" do
expect {
ActionCable.server.broadcast(
"notifications", text: 'Hello!', user_id: 12
)
}.to have_broadcasted_to("notifications").with(a_hash_including(text: 'Hello!'))
end
end
"""
When I run `rspec spec/models/broadcaster_spec.rb`
Then the examples should all pass

Scenario: Checking passed message with block
Given a file named "spec/models/broadcaster_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe "broadcasting" do
it "matches with message" do
expect {
ActionCable.server.broadcast(
"notifications", text: 'Hello!', user_id: 12
)
}.to have_broadcasted_to("notifications").with { |data|
expect(data['user_id']).to eq 12
}
end
end
"""
When I run `rspec spec/models/broadcaster_spec.rb`
Then the examples should all pass

Scenario: Using alias method
Given a file named "spec/models/broadcaster_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe "broadcasting" do
it "matches with stream name" do
expect {
ActionCable.server.broadcast(
"notifications", text: 'Hello!'
)
}.to broadcast_to("notifications")
end
end
"""
When I run `rspec spec/models/broadcaster_spec.rb`
Then the examples should all pass

Scenario: Checking broadcast to a record
Given a file named "spec/channels/chat_channel_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe ChatChannel, :type => :channel do
it "successfully subscribes" do
user = User.new(42)

expect {
ChatChannel.broadcast_to(user, text: 'Hi')
}.to have_broadcasted_to(user)
end
end
"""
And a file named "app/models/user.rb" with:
"""ruby
class User < Struct.new(:name)
def to_gid_param
name
end
end
"""
When I run `rspec spec/channels/chat_channel_spec.rb`
Then the example should pass

Scenario: Checking broadcast to a record in non-channel spec
Given a file named "spec/models/broadcaster_spec.rb" with:
"""ruby
require "rails_helper"

RSpec.describe "broadcasting" do
it "matches with stream name" do
user = User.new(42)

expect {
ChatChannel.broadcast_to(user, text: 'Hi')
}.to broadcast_to(ChatChannel.broadcasting_for(user))
end
end
"""
And a file named "app/models/user.rb" with:
"""ruby
class User < Struct.new(:name)
def to_gid_param
name
end
end
"""
When I run `rspec spec/models/broadcaster_spec.rb`
Then the example should pass