Skip to content

Add ActionCable::Channel::TestCase#33969

Merged
jeremy merged 1 commit intorails:masterfrom
palkan:activerecord/channels-unit-testing
Sep 27, 2018
Merged

Add ActionCable::Channel::TestCase#33969
jeremy merged 1 commit intorails:masterfrom
palkan:activerecord/channels-unit-testing

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?

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

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

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.

3 participants