From f7dd2d67d6a8b74f2762be51dff4f96983175aee Mon Sep 17 00:00:00 2001 From: Vladimir Dementyev Date: Sun, 19 Aug 2018 19:06:30 -0400 Subject: Add Action Cable test adapter --- .../lib/action_cable/subscription_adapter.rb | 1 + .../lib/action_cable/subscription_adapter/test.rb | 40 ++++++++++++++++++ .../test/subscription_adapter/test_adapter_test.rb | 47 ++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 actioncable/lib/action_cable/subscription_adapter/test.rb create mode 100644 actioncable/test/subscription_adapter/test_adapter_test.rb diff --git a/actioncable/lib/action_cable/subscription_adapter.rb b/actioncable/lib/action_cable/subscription_adapter.rb index bcece8d33b..6a9d5c2080 100644 --- a/actioncable/lib/action_cable/subscription_adapter.rb +++ b/actioncable/lib/action_cable/subscription_adapter.rb @@ -5,6 +5,7 @@ module ActionCable extend ActiveSupport::Autoload autoload :Base + autoload :Test autoload :SubscriberMap autoload :ChannelPrefix end diff --git a/actioncable/lib/action_cable/subscription_adapter/test.rb b/actioncable/lib/action_cable/subscription_adapter/test.rb new file mode 100644 index 0000000000..52226a7c71 --- /dev/null +++ b/actioncable/lib/action_cable/subscription_adapter/test.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "async" + +module ActionCable + module SubscriptionAdapter + # == Test adapter for Action Cable + # + # The test adapter should be used only in testing. Along with + # ActionCable::TestHelper it makes a great tool to test your Rails application. + # + # To use the test adapter set adapter value to +test+ in your +cable.yml+. + # + # NOTE: Test adapter extends the ActionCable::SubscriptionsAdapter::Async adapter, + # so it could be used in system tests too. + class Test < Async + def broadcast(channel, payload) + broadcasts(channel) << payload + super + end + + def broadcasts(channel) + channels_data[channel] ||= [] + end + + def clear_messages(channel) + channels_data[channel] = [] + end + + def clear + @channels_data = nil + end + + private + def channels_data + @channels_data ||= {} + end + end + end +end diff --git a/actioncable/test/subscription_adapter/test_adapter_test.rb b/actioncable/test/subscription_adapter/test_adapter_test.rb new file mode 100644 index 0000000000..3fe07adb4a --- /dev/null +++ b/actioncable/test/subscription_adapter/test_adapter_test.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "test_helper" +require_relative "common" + +class ActionCable::SubscriptionAdapter::TestTest < ActionCable::TestCase + include CommonSubscriptionAdapterTest + + def setup + super + + @tx_adapter.shutdown + @tx_adapter = @rx_adapter + end + + def cable_config + { adapter: "test" } + end + + test "#broadcast stores messages for streams" do + @tx_adapter.broadcast("channel", "payload") + @tx_adapter.broadcast("channel2", "payload2") + + assert_equal ["payload"], @tx_adapter.broadcasts("channel") + assert_equal ["payload2"], @tx_adapter.broadcasts("channel2") + end + + test "#clear_messages deletes recorded broadcasts for the channel" do + @tx_adapter.broadcast("channel", "payload") + @tx_adapter.broadcast("channel2", "payload2") + + @tx_adapter.clear_messages("channel") + + assert_equal [], @tx_adapter.broadcasts("channel") + assert_equal ["payload2"], @tx_adapter.broadcasts("channel2") + end + + test "#clear deletes all recorded broadcasts" do + @tx_adapter.broadcast("channel", "payload") + @tx_adapter.broadcast("channel2", "payload2") + + @tx_adapter.clear + + assert_equal [], @tx_adapter.broadcasts("channel") + assert_equal [], @tx_adapter.broadcasts("channel2") + end +end -- cgit v1.2.3 From c11ca0996227ef6f3b6154e4742a9e03949557df Mon Sep 17 00:00:00 2001 From: Vladimir Dementyev Date: Sun, 19 Aug 2018 19:23:33 -0400 Subject: Add Action Cable test case and test helper --- actioncable/lib/action_cable.rb | 2 + actioncable/lib/action_cable/test_case.rb | 11 +++ actioncable/lib/action_cable/test_helper.rb | 132 ++++++++++++++++++++++++++++ actioncable/test/test_helper.rb | 4 + actioncable/test/test_helper_test.rb | 106 ++++++++++++++++++++++ 5 files changed, 255 insertions(+) create mode 100644 actioncable/lib/action_cable/test_case.rb create mode 100644 actioncable/lib/action_cable/test_helper.rb create mode 100644 actioncable/test/test_helper_test.rb diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb index e7456e3c1b..35eacc2f4f 100644 --- a/actioncable/lib/action_cable.rb +++ b/actioncable/lib/action_cable.rb @@ -51,4 +51,6 @@ module ActionCable autoload :Channel autoload :RemoteConnections autoload :SubscriptionAdapter + autoload :TestHelper + autoload :TestCase end diff --git a/actioncable/lib/action_cable/test_case.rb b/actioncable/lib/action_cable/test_case.rb new file mode 100644 index 0000000000..d153259bf6 --- /dev/null +++ b/actioncable/lib/action_cable/test_case.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "active_support/test_case" + +module ActionCable + class TestCase < ActiveSupport::TestCase + include ActionCable::TestHelper + + ActiveSupport.run_load_hooks(:action_cable_test_case, self) + end +end diff --git a/actioncable/lib/action_cable/test_helper.rb b/actioncable/lib/action_cable/test_helper.rb new file mode 100644 index 0000000000..dbd5ec3b16 --- /dev/null +++ b/actioncable/lib/action_cable/test_helper.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +module ActionCable + # Provides helper methods for testing Action Cable broadcasting + module TestHelper + def before_setup # :nodoc: + server = ActionCable.server + test_adapter = ActionCable::SubscriptionAdapter::Test.new(server) + + @old_pubsub_adapter = server.pubsub + + server.instance_variable_set(:@pubsub, test_adapter) + super + end + + def after_teardown # :nodoc: + super + ActionCable.server.instance_variable_set(:@pubsub, @old_pubsub_adapter) + end + + # Asserts that the number of broadcasted messages to the stream matches the given number. + # + # def test_broadcasts + # assert_broadcasts 'messages', 0 + # ActionCable.server.broadcast 'messages', { text: 'hello' } + # assert_broadcasts 'messages', 1 + # ActionCable.server.broadcast 'messages', { text: 'world' } + # assert_broadcasts 'messages', 2 + # end + # + # If a block is passed, that block should cause the specified number of + # messages to be broadcasted. + # + # def test_broadcasts_again + # assert_broadcasts('messages', 1) do + # ActionCable.server.broadcast 'messages', { text: 'hello' } + # end + # + # assert_broadcasts('messages', 2) do + # ActionCable.server.broadcast 'messages', { text: 'hi' } + # ActionCable.server.broadcast 'messages', { text: 'how are you?' } + # end + # end + # + def assert_broadcasts(stream, number) + if block_given? + original_count = broadcasts_size(stream) + yield + new_count = broadcasts_size(stream) + assert_equal number, new_count - original_count, "#{number} broadcasts to #{stream} expected, but #{new_count - original_count} were sent" + else + actual_count = broadcasts_size(stream) + assert_equal number, actual_count, "#{number} broadcasts to #{stream} expected, but #{actual_count} were sent" + end + end + + # Asserts that no messages have been sent to the stream. + # + # def test_no_broadcasts + # assert_no_broadcasts 'messages' + # ActionCable.server.broadcast 'messages', { text: 'hi' } + # assert_broadcasts 'messages', 1 + # end + # + # If a block is passed, that block should not cause any message to be sent. + # + # def test_broadcasts_again + # assert_no_broadcasts 'messages' do + # # No job messages should be sent from this block + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_broadcasts 'messages', 0, &block + # + def assert_no_broadcasts(stream, &block) + assert_broadcasts stream, 0, &block + end + + # Asserts that the specified message has been sent to the stream. + # + # def test_assert_transmited_message + # ActionCable.server.broadcast 'messages', text: 'hello' + # assert_broadcast_on('messages', text: 'hello') + # end + # + # If a block is passed, that block should cause a message with the specified data to be sent. + # + # def test_assert_broadcast_on_again + # assert_broadcast_on('messages', text: 'hello') do + # ActionCable.server.broadcast 'messages', text: 'hello' + # end + # end + # + def assert_broadcast_on(stream, data) + # Encode to JSON and back–we want to use this value to compare + # with decoded JSON. + # Comparing JSON strings doesn't work due to the order if the keys. + serialized_msg = + ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(data)) + + new_messages = broadcasts(stream) + if block_given? + old_messages = new_messages + clear_messages(stream) + + yield + new_messages = broadcasts(stream) + clear_messages(stream) + + # Restore all sent messages + (old_messages + new_messages).each { |m| pubsub_adapter.broadcast(stream, m) } + end + + message = new_messages.find { |msg| ActiveSupport::JSON.decode(msg) == serialized_msg } + + assert message, "No messages sent with #{data} to #{stream}" + end + + def pubsub_adapter # :nodoc: + ActionCable.server.pubsub + end + + delegate :broadcasts, :clear_messages, to: :pubsub_adapter + + private + def broadcasts_size(channel) # :nodoc: + broadcasts(channel).size + end + end +end diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index ac7881c950..f5b9ebf517 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -15,6 +15,10 @@ end # Require all the stubs and models Dir[File.expand_path("stubs/*.rb", __dir__)].each { |file| require file } +# Set test adapter and logger +ActionCable.server.config.cable = { "adapter" => "test" } +ActionCable.server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } + class ActionCable::TestCase < ActiveSupport::TestCase include ActiveSupport::Testing::MethodCallAssertions diff --git a/actioncable/test/test_helper_test.rb b/actioncable/test/test_helper_test.rb new file mode 100644 index 0000000000..f82adb9c8f --- /dev/null +++ b/actioncable/test/test_helper_test.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require "test_helper" + +class BroadcastChannel < ActionCable::Channel::Base +end + +class TransmissionsTest < ActionCable::TestCase + def test_assert_broadcasts + assert_nothing_raised do + assert_broadcasts("test", 1) do + ActionCable.server.broadcast "test", "message" + end + end + end + + def test_assert_broadcasts_with_no_block + assert_nothing_raised do + ActionCable.server.broadcast "test", "message" + assert_broadcasts "test", 1 + end + + assert_nothing_raised do + ActionCable.server.broadcast "test", "message 2" + ActionCable.server.broadcast "test", "message 3" + assert_broadcasts "test", 3 + end + end + + def test_assert_no_broadcasts_with_no_block + assert_nothing_raised do + assert_no_broadcasts "test" + end + end + + def test_assert_no_broadcasts + assert_nothing_raised do + assert_no_broadcasts("test") do + ActionCable.server.broadcast "test2", "message" + end + end + end + + def test_assert_broadcasts_message_too_few_sent + ActionCable.server.broadcast "test", "hello" + error = assert_raises Minitest::Assertion do + assert_broadcasts("test", 2) do + ActionCable.server.broadcast "test", "world" + end + end + + assert_match(/2 .* but 1/, error.message) + end + + def test_assert_broadcasts_message_too_many_sent + error = assert_raises Minitest::Assertion do + assert_broadcasts("test", 1) do + ActionCable.server.broadcast "test", "hello" + ActionCable.server.broadcast "test", "world" + end + end + + assert_match(/1 .* but 2/, error.message) + end +end + +class TransmitedDataTest < ActionCable::TestCase + include ActionCable::TestHelper + + def test_assert_broadcast_on + assert_nothing_raised do + assert_broadcast_on("test", "message") do + ActionCable.server.broadcast "test", "message" + end + end + end + + def test_assert_broadcast_on_with_hash + assert_nothing_raised do + assert_broadcast_on("test", text: "hello") do + ActionCable.server.broadcast "test", text: "hello" + end + end + end + + def test_assert_broadcast_on_with_no_block + assert_nothing_raised do + ActionCable.server.broadcast "test", "hello" + assert_broadcast_on "test", "hello" + end + + assert_nothing_raised do + ActionCable.server.broadcast "test", "world" + assert_broadcast_on "test", "world" + end + end + + def test_assert_broadcast_on_message + ActionCable.server.broadcast "test", "hello" + error = assert_raises Minitest::Assertion do + assert_broadcast_on("test", "world") + end + + assert_match(/No messages sent/, error.message) + end +end -- cgit v1.2.3