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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ActionCable::Channel::TestCase #33969

Merged
merged 1 commit into from
Sep 27, 2018

Conversation

palkan
Copy link
Contributor

@palkan palkan commented Sep 24, 2018

Follow-up #33659.

ActionCable::Channel::TestCase provides an ability
to unit-test channel classes.

From #23211 (comment):

There are several reasons to write unit/functional cable tests:

  • Access control (who has access to the channel? who can perform action and with which argument?
  • Frontend-less applications have no system tests at all–and we still need a way to test channels logic.

See previous discussion here #27191

Summary

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 "chat_1", streams.last
  end

  def test_does_not_subscribe_without_room_number
    subscribe

    # 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_broadcasts_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_broadcasts_on(@room, text: "I'm here!", from: "John") do
      perform :speak, message: "I'm here!"
    end
  end
end

Copy link
Member

@jeremy jeremy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic, @palkan. Would you add a changelog entry to match?

class NonInferrableChannelError < ::StandardError
def initialize(name)
super "Unable to determine the channel to test from #{name}. " +
"You'll need to specify it using tests YourChannel in your " +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backticks around "tests YourChannel" to be clear that it's code to be added to the test case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Done

def test_set_channel_class_manual_using_string
assert_equal TestTestChannel, self.class.channel_class
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use more precise language for these, e.g. Crazy → NonInferrable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stole this craziness from action_mailer tests 🙂

Fixed

perform :broadcast_to_user, message: "SOS"
end
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great test coverage and demonstration of the test case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

ActionCable::Channel::TestCase provides an ability
to unit-test channel classes.

There are several reasons to write unit/functional cable tests:
- Access control (who has access to the channel? who can perform action and with which argument?
- Frontend-less applications have no system tests at all–and we still need a way to test channels logic.

See also rails#27191
@palkan palkan force-pushed the activerecord/channels-unit-testing branch from 2a8f92e to 16e821a Compare September 24, 2018 20:59
@palkan
Copy link
Contributor Author

palkan commented Sep 24, 2018

Would you add a changelog entry to match?

I thought we'll add one changelog entry along with the last Action Cable testing PR (this one is not the last one) since we've skipped it in #33969.

@jeremy Do you think it's better to add multiple change log entries?

Copy link
Member

@jeremy jeremy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good re. changelog!

@jeremy jeremy merged commit 8541394 into rails:master Sep 27, 2018
@jeremy jeremy added this to the 6.0.0 milestone Sep 27, 2018
@subscription.singleton_class.include(ChannelStub)
@subscription.send(:initialize, connection, CHANNEL_IDENTIFIER, params.with_indifferent_access)
# Call subscribe_to_channel if it's public (Rails 5.0.1+)
@subscription.subscribe_to_channel if ActionCable.gem_version >= Gem::Version.new("5.0.1")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check still necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! No, we don't need it in Rails 6.

Will send a follow-up.

Thanks for catching!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants