File: have_broadcasted_to_spec.rb

package info (click to toggle)
ruby-rspec-rails 7.1.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,796 kB
  • sloc: ruby: 11,068; sh: 198; makefile: 6
file content (263 lines) | stat: -rw-r--r-- 8,859 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
require "rspec/rails/feature_check"

if RSpec::Rails::FeatureCheck.has_action_cable_testing?
  require "rspec/rails/matchers/action_cable"

  class CableGlobalIdModel
    include GlobalID::Identification

    attr_reader :id

    def initialize(id)
      @id = id
    end

    def to_global_id(_options = {})
      @global_id ||= GlobalID.create(self, app: "rspec-suite")
    end
  end
end

RSpec.describe "have_broadcasted_to matchers", skip: !RSpec::Rails::FeatureCheck.has_action_cable_testing? do
  let(:channel) do
    Class.new(ActionCable::Channel::Base) do
      def self.channel_name
        "broadcast"
      end
    end
  end

  def broadcast(stream, msg)
    ActionCable.server.broadcast stream, msg
  end

  before do
    server = ActionCable.server
    test_adapter = ActionCable::SubscriptionAdapter::Test.new(server)
    server.instance_variable_set(:@pubsub, test_adapter)
  end

  describe "have_broadcasted_to" do
    it "raises ArgumentError when no Proc passed to expect" do
      expect {
        expect(true).to have_broadcasted_to('stream')
      }.to raise_error(ArgumentError)
    end

    it "passes with default messages count (exactly one)" do
      expect {
        broadcast('stream', 'hello')
      }.to have_broadcasted_to('stream')
    end

    it "passes when using symbol target" do
      expect {
        broadcast(:stream, 'hello')
      }.to have_broadcasted_to(:stream)
    end

    it "passes when using alias" do
      expect {
        broadcast('stream', 'hello')
      }.to broadcast_to('stream')
    end

    it "counts only messages sent in block" do
      broadcast('stream', 'one')
      expect {
        broadcast('stream', 'two')
      }.to have_broadcasted_to('stream').exactly(1)
    end

    it "passes when negated" do
      expect { }.not_to have_broadcasted_to('stream')
    end

    it "fails when message is not sent" do
      expect {
        expect { }.to have_broadcasted_to('stream')
      }.to raise_error(/expected to broadcast exactly 1 messages to stream, but broadcast 0/)
    end

    it "fails when too many messages broadcast" do
      expect {
        expect {
          broadcast('stream', 'one')
          broadcast('stream', 'two')
        }.to have_broadcasted_to('stream').exactly(1)
      }.to raise_error(/expected to broadcast exactly 1 messages to stream, but broadcast 2/)
    end

    it "reports correct number in fail error message" do
      broadcast('stream', 'one')
      expect {
        expect { }.to have_broadcasted_to('stream').exactly(1)
      }.to raise_error(/expected to broadcast exactly 1 messages to stream, but broadcast 0/)
    end

    it "fails when negated and message is sent" do
      expect {
        expect { broadcast('stream', 'one') }.not_to have_broadcasted_to('stream')
      }.to raise_error(/expected not to broadcast exactly 1 messages to stream, but broadcast 1/)
    end

    it "passes with multiple streams" do
      expect {
        broadcast('stream_a', 'A')
        broadcast('stream_b', 'B')
        broadcast('stream_c', 'C')
      }.to have_broadcasted_to('stream_a').and have_broadcasted_to('stream_b')
    end

    it "passes with :once count" do
      expect {
        broadcast('stream', 'one')
      }.to have_broadcasted_to('stream').exactly(:once)
    end

    it "passes with :twice count" do
      expect {
        broadcast('stream', 'one')
        broadcast('stream', 'two')
      }.to have_broadcasted_to('stream').exactly(:twice)
    end

    it "passes with :thrice count" do
      expect {
        broadcast('stream', 'one')
        broadcast('stream', 'two')
        broadcast('stream', 'three')
      }.to have_broadcasted_to('stream').exactly(:thrice)
    end

    it "passes with at_least count when sent messages are over limit" do
      expect {
        broadcast('stream', 'one')
        broadcast('stream', 'two')
      }.to have_broadcasted_to('stream').at_least(:once)
    end

    it "passes with at_most count when sent messages are under limit" do
      expect {
        broadcast('stream', 'hello')
      }.to have_broadcasted_to('stream').at_most(:once)
    end

    it "generates failure message with at least hint" do
      expect {
        expect { }.to have_broadcasted_to('stream').at_least(:once)
      }.to raise_error(/expected to broadcast at least 1 messages to stream, but broadcast 0/)
    end

    it "generates failure message with at most hint" do
      expect {
        expect {
          broadcast('stream', 'hello')
          broadcast('stream', 'hello')
        }.to have_broadcasted_to('stream').at_most(:once)
      }.to raise_error(/expected to broadcast at most 1 messages to stream, but broadcast 2/)
    end

    it "passes with provided data" do
      expect {
        broadcast('stream', id: 42, name: "David")
      }.to have_broadcasted_to('stream').with(id: 42, name: "David")
    end

    it "passes with provided data matchers" do
      expect {
        broadcast('stream', id: 42, name: "David", message_id: 123)
      }.to have_broadcasted_to('stream').with(a_hash_including(name: "David", id: 42))
    end

    it "passes with provided data matchers with anything" do
      expect {
        broadcast('stream', id: 42, name: "David", message_id: 123)
      }.to have_broadcasted_to('stream').with({ name: anything, id: anything, message_id: anything })
    end

    it "generates failure message when data not match" do
      expect {
        expect {
          broadcast('stream', id: 42, name: "David", message_id: 123)
        }.to have_broadcasted_to('stream').with(a_hash_including(name: "John", id: 42))
      }.to raise_error(/expected to broadcast exactly 1 messages to stream with a hash including/)
    end

    it "throws descriptive error when no test adapter set" do
      require "action_cable/subscription_adapter/inline"
      ActionCable.server.instance_variable_set(:@pubsub, ActionCable::SubscriptionAdapter::Inline)
      expect {
        expect { broadcast('stream', 'hello') }.to have_broadcasted_to('stream')
      }.to raise_error("To use ActionCable matchers set `adapter: test` in your cable.yml")
    end

    it "fails with with block with incorrect data" do
      expect {
        expect {
          broadcast('stream', "asdf")
        }.to have_broadcasted_to('stream').with { |data|
          expect(data).to eq("zxcv")
        }
      }.to raise_error { |e|
        expect(e.message).to match(/expected: "zxcv"/)
        expect(e.message).to match(/got: "asdf"/)
      }
    end

    context "when object is passed as first argument" do
      let(:model) { CableGlobalIdModel.new(42) }

      context "when channel is present" do
        it "passes" do
          expect {
            channel.broadcast_to(model, text: 'Hi')
          }.to have_broadcasted_to(model).from_channel(channel)
        end
      end

      context "when channel can't be inferred" do
        it "raises exception" do
          expect {
            expect {
              channel.broadcast_to(model, text: 'Hi')
            }.to have_broadcasted_to(model)
          }.to raise_error(ArgumentError)
        end
      end
    end

    it "has an appropriate description" do
      expect(have_broadcasted_to("my_stream").description).to eq("have broadcasted exactly 1 messages to my_stream")
    end

    it "has an appropriate description when aliased" do
      expect(broadcast_to("my_stream").description).to eq("broadcast exactly 1 messages to my_stream")
    end

    it "has an appropriate description when stream name is passed as an array" do
      expect(have_broadcasted_to(%w[my_stream stream_2]).from_channel(channel).description).to eq("have broadcasted exactly 1 messages to broadcast:my_stream:stream_2")
    end

    it "has an appropriate description not mentioning the channel when qualified with `#from_channel`" do
      expect(have_broadcasted_to("my_stream").from_channel(channel).description).to eq("have broadcasted exactly 1 messages to my_stream")
    end

    it "has an appropriate description including the expected contents when qualified with `#with`" do
      expect(have_broadcasted_to("my_stream").from_channel(channel).with("hello world").description).to eq("have broadcasted exactly 1 messages to my_stream with \"hello world\"")
    end

    it "has an appropriate description including the matcher's description when qualified with `#with` and a composable matcher" do
      description = have_broadcasted_to("my_stream")
          .from_channel(channel)
          .with(a_hash_including(a: :b))
          .description

      if RUBY_VERSION >= '3.4'
        expect(description).to eq("have broadcasted exactly 1 messages to my_stream with a hash including {a: :b}")
      else
        expect(description).to eq("have broadcasted exactly 1 messages to my_stream with a hash including {:a => :b}")
      end
    end
  end
end