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 guides and generators #34933

Merged
merged 3 commits into from
Jan 14, 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 actioncable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
* Merge [`action-cable-testing`](https://github.com/palkan/action-cable-testing) to Rails.

*Vladimir Dementyev*

* The JavaScript WebSocket client will no longer try to reconnect
when you call `reject_unauthorized_connection` on the connection.

Expand Down
2 changes: 1 addition & 1 deletion actioncable/lib/action_cable/connection/test_case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def initialize(request)
# def test_connects_with_proper_cookie
# # Simulate the connection request with a cookie.
# cookies["user_id"] = users(:john).id

#
# connect
#
# # Assert the connection identifier matches the fixture.
Expand Down
3 changes: 2 additions & 1 deletion actioncable/lib/rails/generators/channel/USAGE
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Example:
========
rails generate channel Chat speak

creates a Chat channel class and JavaScript asset:
creates a Chat channel class, test and JavaScript asset:
Channel: app/channels/chat_channel.rb
Test: test/channels/chat_channel_test.rb
Assets: app/javascript/channels/chat_channel.js
2 changes: 2 additions & 0 deletions actioncable/lib/rails/generators/channel/channel_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class ChannelGenerator < NamedBase

check_class_collision suffix: "Channel"

hook_for :test_framework

def create_channel_file
template "channel.rb", File.join("app/channels", class_path, "#{file_name}_channel.rb")

Expand Down
20 changes: 20 additions & 0 deletions actioncable/lib/rails/generators/test_unit/channel_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module TestUnit
module Generators
class ChannelGenerator < ::Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)

check_class_collision suffix: "ChannelTest"

def create_test_files
template "channel_test.rb", File.join("test/channels", class_path, "#{file_name}_channel_test.rb")
end

private
def file_name # :doc:
@_file_name ||= super.sub(/_channel\z/i, "")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

require "test_helper"

class <%= class_name %>ChannelTest < ActionCable::Channel::TestCase
# test "subscribes" do
# subscribe
# assert subscription.confirmed?
# end
end
8 changes: 8 additions & 0 deletions guides/source/6_0_release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Highlights in Rails 6.0:
* Action Mailbox
* Action Text
* Parallel Testing
* Action Cable Testing

These release notes cover only the major changes. To learn about various bug
fixes and changes, please refer to the change logs or check out the [list of
Expand Down Expand Up @@ -62,6 +63,13 @@ test suite. While forking processes is the default method, threading is
supported as well. Running tests in parallel reduces the time it takes
your entire test suite to run.

### Action Cable Testing

[Pull Request](https://github.com/rails/rails/pull/33659)

[Action Cable testing tools](testing.html#testing-action-cable) allow you to test your
Action Cable functionality at any level: connections, channels, broadcasts.

Railties
--------

Expand Down
5 changes: 5 additions & 0 deletions guides/source/action_cable_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -768,3 +768,8 @@ internally, irrespective of whether the application server is multi-threaded or

Accordingly, Action Cable works with popular servers like Unicorn, Puma, and
Passenger.

## Testing

You can find detailed instructions on how to test your Action Cable functionality in the
[testing guide](testing.html#testing-action-cable).
114 changes: 111 additions & 3 deletions guides/source/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ Rails creates a `test` directory for you as soon as you create a Rails project u

```bash
$ ls -F test
application_system_test_case.rb fixtures/ integration/ models/ test_helper.rb
controllers/ helpers/ mailers/ system/
application_system_test_case.rb controllers/ helpers/ mailers/ system/
channels/ fixtures/ integration/ models/ test_helper.rb
```

The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers.
The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `channels` directory is meant to hold tests for Action Cable connection and channels. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers.

The system test directory holds system tests, which are used for full browser
testing of your application. System tests allow you to test your application
Expand Down Expand Up @@ -1731,6 +1731,114 @@ class ProductTest < ActiveJob::TestCase
end
```

Testing Action Cable
--------------------

Since Action Cable is used at different levels inside your application,
you'll need to test both the channels and connection classes themsleves and that other
entities broadcast correct messages.

### Connection Test Case

By default, when you generate new Rails application with Action Cable, a test for the base connection class (`ApplicationCable::Connection`) is generated as well under `test/channels/application_cable` directory.

Connection tests aim to check whether a connection's identifiers gets assigned properly
or that any improper connection requests are rejected. Here is an example:

```ruby
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
test "connects with params" do
# Simulate a connection opening by calling the `connect` method
connect params: { user_id: 42 }

# You can access the Connection object via `connection` in tests
assert_equal connection.user_id, "42"
end

test "rejects connection without params" do
# Use `assert_reject_connection` matcher to verify that
# connection is rejected
assert_reject_connection { connect }
end
end
```

You can also specify request cookies the same way you do in integration tests:


```ruby
test "connects with_cookies" do
cookies.signed[:user_id] = "42"

connect

assert_equal connection.user_id, "42"
end
```

See the API documentation for [`AcionCable::Connection::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Connection/TestCase.html) for more information.


### Channel Test Case

By default, when you generate a channel, an associated test will be generated as well
under the `test/channels` directory. Here's an example test with a chat channel:

```ruby
require "test_helper"

class ChatChannelTest < ActionCable::Channel::TestCase
test "subscribes and stream for room" do
# Simulate a subscription creation by calling `subscribe`
subscribe room: "15"

# You can access the Channel object via `subscription` in tests
assert subscription.confirmed?
assert_has_stream "chat_15"
end
end
```

This test is pretty simple and only asserts that the channel subscribes the connection to a particular stream.

You can also specify the underlying connection identifiers. Here's an example test with a web notifications channel:

```ruby
require "test_helper"

class WebNotificationsChannelTest < ActionCable::Channel::TestCase
test "subscribes and stream for user" do
stub_connection current_user: users[:john]

subscribe

assert_has_stream_for users[:john]
end
end
```

See the API documentation for [`AcionCable::Channel::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Channel/TestCase.html) for more information.

### Custom Assertions And Testing Broadcasts Inside Other Components

Action Cable ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActionCable::TestHelper`](http://api.rubyonrails.org/classes/ActionCable/TestHelper.html).

It's a good practice to ensure that the correct message has been broadcasted inside another components (e.g. inside your controllers). This is precisely where
the custom assertions provided by Action Cable are pretty useful. For instance,
within a model:

```ruby
require 'test_helper'

class ProductTest < ActionCable::TestCase
test "broadcast status after charge" do
assert_broadcast_on("products:#{product.id}", type: "charged") do
product.charge(account)
end
end
end
```

Additional Testing Resources
----------------------------

Expand Down
2 changes: 2 additions & 0 deletions railties/lib/rails/generators/rails/app/app_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def test
empty_directory_with_keep_file "test/helpers"
empty_directory_with_keep_file "test/integration"

template "test/channels/application_cable/connection_test.rb"
template "test/test_helper.rb"
end

Expand Down Expand Up @@ -440,6 +441,7 @@ def delete_action_cable_files_skipping_action_cable
if options[:skip_action_cable]
remove_dir "app/javascript/channels"
remove_dir "app/channels"
remove_dir "test/channels"
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

require "test_helper"

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
# test "connects with cookies" do
# cookies.signed[:user_id] = 42
#
# connect
#
# assert_equal connection.user_id, "42"
# end
end
3 changes: 3 additions & 0 deletions railties/test/generators/app_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
test/test_helper.rb
test/fixtures
test/fixtures/files
test/channels/application_cable/connection_test.rb
test/controllers
test/models
test/helpers
Expand Down Expand Up @@ -363,6 +364,8 @@ def test_app_update_does_not_generate_action_cable_contents_when_skip_action_cab
assert_file "#{app_root}/config/environments/production.rb" do |content|
assert_no_match(/config\.action_cable/, content)
end

assert_no_file "#{app_root}/test/channels/application_cable/connection_test.rb"
end

def test_app_update_does_not_generate_bootsnap_contents_when_skip_bootsnap_is_given
Expand Down
14 changes: 14 additions & 0 deletions railties/test/generators/channel_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,23 @@ def test_consumer_js_is_created_if_not_present_already
assert_file "app/javascript/channels/consumer.js"
end

def test_invokes_default_test_framework
run_generator %w(chat -t=test_unit)

assert_file "test/channels/chat_channel_test.rb" do |test|
assert_match(/class ChatChannelTest < ActionCable::Channel::TestCase/, test)
assert_match(/# test "subscribes" do/, test)
assert_match(/# assert subscription.confirmed\?/, test)
end
end

def test_channel_on_revoke
run_generator ["chat"]
run_generator ["chat"], behavior: :revoke

assert_no_file "app/channels/chat_channel.rb"
assert_no_file "app/javascript/channels/chat_channel.js"
assert_no_file "test/channels/chat_channel_test.rb"

assert_file "app/channels/application_cable/channel.rb"
assert_file "app/channels/application_cable/connection.rb"
Expand All @@ -88,5 +99,8 @@ def test_channel_suffix_is_not_duplicated

assert_no_file "app/javascript/channels/chat_channel_channel.js"
assert_file "app/javascript/channels/chat_channel.js"

assert_no_file "test/channels/chat_channel_channel_test.rb"
assert_file "test/channels/chat_channel_test.rb"
end
end