rspec-rails 3.7.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/Capybara.md +5 -54
- data/Changelog.md +259 -70
- data/README.md +265 -496
- data/lib/generators/rspec/channel/channel_generator.rb +12 -0
- data/lib/generators/rspec/{observer/templates/observer_spec.rb → channel/templates/channel_spec.rb.erb} +1 -1
- data/lib/generators/rspec/controller/controller_generator.rb +21 -4
- data/lib/generators/rspec/controller/templates/request_spec.rb +14 -0
- data/lib/generators/rspec/controller/templates/routing_spec.rb +13 -0
- data/lib/generators/rspec/feature/feature_generator.rb +4 -4
- data/lib/generators/rspec/generator/generator_generator.rb +24 -0
- data/lib/generators/rspec/generator/templates/generator_spec.rb +6 -0
- data/lib/generators/rspec/helper/helper_generator.rb +1 -1
- data/lib/generators/rspec/install/install_generator.rb +4 -4
- data/lib/generators/rspec/install/templates/spec/rails_helper.rb +25 -12
- data/lib/generators/rspec/integration/integration_generator.rb +4 -4
- data/lib/generators/rspec/integration/templates/request_spec.rb +1 -1
- data/lib/generators/rspec/mailbox/mailbox_generator.rb +14 -0
- data/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb +7 -0
- data/lib/generators/rspec/mailer/mailer_generator.rb +2 -1
- data/lib/generators/rspec/mailer/templates/preview.rb +1 -1
- data/lib/generators/rspec/model/model_generator.rb +6 -5
- data/lib/generators/rspec/model/templates/fixtures.yml +1 -1
- data/lib/generators/rspec/request/request_generator.rb +1 -1
- data/lib/generators/rspec/scaffold/scaffold_generator.rb +43 -23
- data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +2 -38
- data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +131 -0
- data/lib/generators/rspec/scaffold/templates/controller_spec.rb +17 -17
- data/lib/generators/rspec/scaffold/templates/edit_spec.rb +1 -1
- data/lib/generators/rspec/scaffold/templates/index_spec.rb +2 -2
- data/lib/generators/rspec/scaffold/templates/new_spec.rb +1 -1
- data/lib/generators/rspec/scaffold/templates/request_spec.rb +133 -0
- data/lib/generators/rspec/scaffold/templates/routing_spec.rb +10 -13
- data/lib/generators/rspec/scaffold/templates/show_spec.rb +1 -1
- data/lib/generators/rspec/system/system_generator.rb +26 -0
- data/lib/generators/rspec/system/templates/system_spec.rb +9 -0
- data/lib/generators/rspec/view/view_generator.rb +2 -2
- data/lib/generators/rspec.rb +0 -6
- data/lib/rspec/rails/adapters.rb +11 -76
- data/lib/rspec/rails/configuration.rb +47 -36
- data/lib/rspec/rails/example/channel_example_group.rb +93 -0
- data/lib/rspec/rails/example/controller_example_group.rb +4 -4
- data/lib/rspec/rails/example/feature_example_group.rb +6 -26
- data/lib/rspec/rails/example/helper_example_group.rb +2 -9
- data/lib/rspec/rails/example/mailbox_example_group.rb +80 -0
- data/lib/rspec/rails/example/mailer_example_group.rb +1 -1
- data/lib/rspec/rails/example/rails_example_group.rb +1 -1
- data/lib/rspec/rails/example/system_example_group.rb +96 -60
- data/lib/rspec/rails/example/view_example_group.rb +47 -28
- data/lib/rspec/rails/example.rb +3 -3
- data/lib/rspec/rails/extensions/active_record/proxy.rb +1 -9
- data/lib/rspec/rails/feature_check.rb +12 -29
- data/lib/rspec/rails/fixture_file_upload_support.rb +40 -0
- data/lib/rspec/rails/fixture_support.rb +37 -31
- data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +170 -0
- data/lib/rspec/rails/matchers/action_cable/have_streams.rb +58 -0
- data/lib/rspec/rails/matchers/action_cable.rb +65 -0
- data/lib/rspec/rails/matchers/action_mailbox.rb +64 -0
- data/lib/rspec/rails/matchers/active_job.rb +180 -22
- data/lib/rspec/rails/matchers/base_matcher.rb +179 -0
- data/lib/rspec/rails/matchers/be_a_new.rb +1 -1
- data/lib/rspec/rails/matchers/be_new_record.rb +1 -1
- data/lib/rspec/rails/matchers/be_valid.rb +1 -1
- data/lib/rspec/rails/matchers/have_enqueued_mail.rb +198 -0
- data/lib/rspec/rails/matchers/have_http_status.rb +34 -13
- data/lib/rspec/rails/matchers/have_rendered.rb +2 -1
- data/lib/rspec/rails/matchers/redirect_to.rb +1 -1
- data/lib/rspec/rails/matchers/routing_matchers.rb +14 -14
- data/lib/rspec/rails/matchers.rb +11 -0
- data/lib/rspec/rails/tasks/rspec.rake +7 -17
- data/lib/rspec/rails/vendor/capybara.rb +10 -15
- data/lib/rspec/rails/version.rb +1 -1
- data/lib/rspec/rails/view_path_builder.rb +1 -1
- data/lib/rspec/rails/view_rendering.rb +16 -5
- data/lib/rspec/rails.rb +1 -0
- data/lib/rspec-rails.rb +13 -10
- data.tar.gz.sig +0 -0
- metadata +55 -33
- metadata.gz.sig +0 -0
- data/lib/generators/rspec/observer/observer_generator.rb +0 -13
@@ -0,0 +1,170 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Rails
|
3
|
+
module Matchers
|
4
|
+
module ActionCable
|
5
|
+
# rubocop: disable Metrics/ClassLength
|
6
|
+
# @private
|
7
|
+
class HaveBroadcastedTo < RSpec::Matchers::BuiltIn::BaseMatcher
|
8
|
+
def initialize(target, channel:)
|
9
|
+
@target = target
|
10
|
+
@channel = channel
|
11
|
+
@block = proc { }
|
12
|
+
@data = nil
|
13
|
+
set_expected_number(:exactly, 1)
|
14
|
+
end
|
15
|
+
|
16
|
+
def with(data = nil, &block)
|
17
|
+
@data = data
|
18
|
+
@data = @data.with_indifferent_access if @data.is_a?(Hash)
|
19
|
+
@block = block if block
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def exactly(count)
|
24
|
+
set_expected_number(:exactly, count)
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def at_least(count)
|
29
|
+
set_expected_number(:at_least, count)
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def at_most(count)
|
34
|
+
set_expected_number(:at_most, count)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def times
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def once
|
43
|
+
exactly(:once)
|
44
|
+
end
|
45
|
+
|
46
|
+
def twice
|
47
|
+
exactly(:twice)
|
48
|
+
end
|
49
|
+
|
50
|
+
def thrice
|
51
|
+
exactly(:thrice)
|
52
|
+
end
|
53
|
+
|
54
|
+
def failure_message
|
55
|
+
"expected to broadcast #{base_message}".tap do |msg|
|
56
|
+
if @unmatching_msgs.any?
|
57
|
+
msg << "\nBroadcasted messages to #{stream}:"
|
58
|
+
@unmatching_msgs.each do |data|
|
59
|
+
msg << "\n #{data}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def failure_message_when_negated
|
66
|
+
"expected not to broadcast #{base_message}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def message_expectation_modifier
|
70
|
+
case @expectation_type
|
71
|
+
when :exactly then "exactly"
|
72
|
+
when :at_most then "at most"
|
73
|
+
when :at_least then "at least"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def supports_block_expectations?
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
def matches?(proc)
|
82
|
+
raise ArgumentError, "have_broadcasted_to and broadcast_to only support block expectations" unless Proc === proc
|
83
|
+
|
84
|
+
original_sent_messages_count = pubsub_adapter.broadcasts(stream).size
|
85
|
+
proc.call
|
86
|
+
in_block_messages = pubsub_adapter.broadcasts(stream).drop(original_sent_messages_count)
|
87
|
+
|
88
|
+
check(in_block_messages)
|
89
|
+
end
|
90
|
+
|
91
|
+
def from_channel(channel)
|
92
|
+
@channel = channel
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def stream
|
99
|
+
@stream ||= if @target.is_a?(String)
|
100
|
+
@target
|
101
|
+
else
|
102
|
+
check_channel_presence
|
103
|
+
@channel.broadcasting_for(@target)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def check(messages)
|
108
|
+
@matching_msgs, @unmatching_msgs = messages.partition do |msg|
|
109
|
+
decoded = ActiveSupport::JSON.decode(msg)
|
110
|
+
decoded = decoded.with_indifferent_access if decoded.is_a?(Hash)
|
111
|
+
|
112
|
+
if @data.nil? || @data === decoded
|
113
|
+
@block.call(decoded)
|
114
|
+
true
|
115
|
+
else
|
116
|
+
false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
@matching_msgs_count = @matching_msgs.size
|
121
|
+
|
122
|
+
case @expectation_type
|
123
|
+
when :exactly then @expected_number == @matching_msgs_count
|
124
|
+
when :at_most then @expected_number >= @matching_msgs_count
|
125
|
+
when :at_least then @expected_number <= @matching_msgs_count
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_expected_number(relativity, count)
|
130
|
+
@expectation_type = relativity
|
131
|
+
@expected_number =
|
132
|
+
case count
|
133
|
+
when :once then 1
|
134
|
+
when :twice then 2
|
135
|
+
when :thrice then 3
|
136
|
+
else Integer(count)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def base_message
|
141
|
+
"#{message_expectation_modifier} #{@expected_number} messages to #{stream}".tap do |msg|
|
142
|
+
msg << " with #{data_description(@data)}" unless @data.nil?
|
143
|
+
msg << ", but broadcast #{@matching_msgs_count}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def data_description(data)
|
148
|
+
if data.is_a?(RSpec::Matchers::Composable)
|
149
|
+
data.description
|
150
|
+
else
|
151
|
+
data
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def pubsub_adapter
|
156
|
+
::ActionCable.server.pubsub
|
157
|
+
end
|
158
|
+
|
159
|
+
def check_channel_presence
|
160
|
+
return if @channel.present? && @channel.respond_to?(:channel_name)
|
161
|
+
|
162
|
+
error_msg = "Broadcasting channel can't be infered. Please, specify it with `from_channel`"
|
163
|
+
raise ArgumentError, error_msg
|
164
|
+
end
|
165
|
+
end
|
166
|
+
# rubocop: enable Metrics/ClassLength
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Rails
|
3
|
+
module Matchers
|
4
|
+
module ActionCable
|
5
|
+
# @api private
|
6
|
+
# Provides the implementation for `have_stream`, `have_stream_for`, and `have_stream_from`.
|
7
|
+
# Not intended to be instantiated directly.
|
8
|
+
class HaveStream < RSpec::Matchers::BuiltIn::BaseMatcher
|
9
|
+
# @api private
|
10
|
+
# @return [String]
|
11
|
+
def failure_message
|
12
|
+
"expected to have #{base_message}"
|
13
|
+
end
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
# @return [String]
|
17
|
+
def failure_message_when_negated
|
18
|
+
"expected not to have #{base_message}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
# @return [Boolean]
|
23
|
+
def matches?(subscription)
|
24
|
+
raise(ArgumentError, "have_streams is used for negated expectations only") if no_expected?
|
25
|
+
|
26
|
+
match(subscription)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
# @return [Boolean]
|
31
|
+
def does_not_match?(subscription)
|
32
|
+
!match(subscription)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def match(subscription)
|
38
|
+
case subscription
|
39
|
+
when ::ActionCable::Channel::Base
|
40
|
+
@actual = subscription.streams
|
41
|
+
no_expected? ? actual.any? : actual.any? { |i| expected === i }
|
42
|
+
else
|
43
|
+
raise ArgumentError, "have_stream, have_stream_from and have_stream_from support expectations on subscription only"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def base_message
|
48
|
+
no_expected? ? "any stream started" : "stream #{expected_formatted} started, but have #{actual_formatted}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def no_expected?
|
52
|
+
!defined?(@expected)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "rspec/rails/matchers/action_cable/have_broadcasted_to"
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Rails
|
5
|
+
module Matchers
|
6
|
+
# Namespace for various implementations of ActionCable features
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
module ActionCable
|
10
|
+
end
|
11
|
+
|
12
|
+
# @api public
|
13
|
+
# Passes if a message has been sent to a stream/object inside a block.
|
14
|
+
# May chain `at_least`, `at_most` or `exactly` to specify a number of times.
|
15
|
+
# To specify channel from which message has been broadcasted to object use `from_channel`.
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# expect {
|
20
|
+
# ActionCable.server.broadcast "messages", text: 'Hi!'
|
21
|
+
# }.to have_broadcasted_to("messages")
|
22
|
+
#
|
23
|
+
# expect {
|
24
|
+
# SomeChannel.broadcast_to(user)
|
25
|
+
# }.to have_broadcasted_to(user).from_channel(SomeChannel)
|
26
|
+
#
|
27
|
+
# # Using alias
|
28
|
+
# expect {
|
29
|
+
# ActionCable.server.broadcast "messages", text: 'Hi!'
|
30
|
+
# }.to broadcast_to("messages")
|
31
|
+
#
|
32
|
+
# expect {
|
33
|
+
# ActionCable.server.broadcast "messages", text: 'Hi!'
|
34
|
+
# ActionCable.server.broadcast "all", text: 'Hi!'
|
35
|
+
# }.to have_broadcasted_to("messages").exactly(:once)
|
36
|
+
#
|
37
|
+
# expect {
|
38
|
+
# 3.times { ActionCable.server.broadcast "messages", text: 'Hi!' }
|
39
|
+
# }.to have_broadcasted_to("messages").at_least(2).times
|
40
|
+
#
|
41
|
+
# expect {
|
42
|
+
# ActionCable.server.broadcast "messages", text: 'Hi!'
|
43
|
+
# }.to have_broadcasted_to("messages").at_most(:twice)
|
44
|
+
#
|
45
|
+
# expect {
|
46
|
+
# ActionCable.server.broadcast "messages", text: 'Hi!'
|
47
|
+
# }.to have_broadcasted_to("messages").with(text: 'Hi!')
|
48
|
+
def have_broadcasted_to(target = nil)
|
49
|
+
check_action_cable_adapter
|
50
|
+
|
51
|
+
ActionCable::HaveBroadcastedTo.new(target, channel: described_class)
|
52
|
+
end
|
53
|
+
alias_method :broadcast_to, :have_broadcasted_to
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @private
|
58
|
+
def check_action_cable_adapter
|
59
|
+
return if ::ActionCable::SubscriptionAdapter::Test === ::ActionCable.server.pubsub
|
60
|
+
|
61
|
+
raise StandardError, "To use ActionCable matchers set `adapter: test` in your cable.yml"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Rails
|
3
|
+
module Matchers
|
4
|
+
# Namespace for various implementations of ActionMailbox features
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
module ActionMailbox
|
8
|
+
# @private
|
9
|
+
class Base < RSpec::Rails::Matchers::BaseMatcher
|
10
|
+
private
|
11
|
+
|
12
|
+
def create_inbound_email(message)
|
13
|
+
RSpec::Rails::MailboxExampleGroup.create_inbound_email(message)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @private
|
18
|
+
class ReceiveInboundEmail < Base
|
19
|
+
def initialize(message)
|
20
|
+
super()
|
21
|
+
|
22
|
+
@inbound_email = create_inbound_email(message)
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches?(mailbox)
|
26
|
+
@mailbox = mailbox
|
27
|
+
@receiver = ApplicationMailbox.router.send(:match_to_mailbox, inbound_email)
|
28
|
+
|
29
|
+
@receiver == @mailbox
|
30
|
+
end
|
31
|
+
|
32
|
+
def failure_message
|
33
|
+
"expected #{describe_inbound_email} to route to #{mailbox}".tap do |msg|
|
34
|
+
if receiver
|
35
|
+
msg << ", but routed to #{receiver} instead"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def failure_message_when_negated
|
41
|
+
"expected #{describe_inbound_email} not to route to #{mailbox}"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :inbound_email, :mailbox, :receiver
|
47
|
+
|
48
|
+
def describe_inbound_email
|
49
|
+
"mail to #{inbound_email.mail.to.to_sentence}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api public
|
55
|
+
# Passes if the given inbound email would be routed to the subject inbox.
|
56
|
+
#
|
57
|
+
# @param message [Hash, Mail::Message] a mail message or hash of
|
58
|
+
# attributes used to build one
|
59
|
+
def receive_inbound_email(message)
|
60
|
+
ActionMailbox::ReceiveInboundEmail.new(message)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -8,14 +8,14 @@ module RSpec
|
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
module ActiveJob
|
11
|
-
# rubocop: disable
|
11
|
+
# rubocop: disable Metrics/ClassLength
|
12
12
|
# @private
|
13
|
-
class Base < RSpec::Matchers::
|
13
|
+
class Base < RSpec::Rails::Matchers::BaseMatcher
|
14
14
|
def initialize
|
15
15
|
@args = []
|
16
16
|
@queue = nil
|
17
17
|
@at = nil
|
18
|
-
@block =
|
18
|
+
@block = proc { }
|
19
19
|
set_expected_number(:exactly, 1)
|
20
20
|
end
|
21
21
|
|
@@ -26,7 +26,7 @@ module RSpec
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def on_queue(queue)
|
29
|
-
@queue = queue
|
29
|
+
@queue = queue.to_s
|
30
30
|
self
|
31
31
|
end
|
32
32
|
|
@@ -67,7 +67,7 @@ module RSpec
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def failure_message
|
70
|
-
"expected to
|
70
|
+
"expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
|
71
71
|
if @unmatching_jobs.any?
|
72
72
|
msg << "\nQueued jobs:"
|
73
73
|
@unmatching_jobs.each do |job|
|
@@ -78,7 +78,7 @@ module RSpec
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def failure_message_when_negated
|
81
|
-
"expected not to
|
81
|
+
"expected not to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}"
|
82
82
|
end
|
83
83
|
|
84
84
|
def message_expectation_modifier
|
@@ -97,8 +97,8 @@ module RSpec
|
|
97
97
|
|
98
98
|
def check(jobs)
|
99
99
|
@matching_jobs, @unmatching_jobs = jobs.partition do |job|
|
100
|
-
if arguments_match?(job) &&
|
101
|
-
args =
|
100
|
+
if job_match?(job) && arguments_match?(job) && queue_match?(job) && at_match?(job)
|
101
|
+
args = deserialize_arguments(job)
|
102
102
|
@block.call(*args)
|
103
103
|
true
|
104
104
|
else
|
@@ -118,14 +118,14 @@ module RSpec
|
|
118
118
|
"#{message_expectation_modifier} #{@expected_number} jobs,".tap do |msg|
|
119
119
|
msg << " with #{@args}," if @args.any?
|
120
120
|
msg << " on queue #{@queue}," if @queue
|
121
|
-
msg << " at #{@at}," if @at
|
122
|
-
msg << " but
|
121
|
+
msg << " at #{@at.inspect}," if @at
|
122
|
+
msg << " but #{self.class::MESSAGE_EXPECTATION_ACTION} #{@matching_jobs_count}"
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
126
|
def base_job_message(job)
|
127
127
|
msg_parts = []
|
128
|
-
msg_parts << "with #{
|
128
|
+
msg_parts << "with #{deserialize_arguments(job)}" if job[:args].any?
|
129
129
|
msg_parts << "on queue #{job[:queue]}" if job[:queue]
|
130
130
|
msg_parts << "at #{Time.at(job[:at])}" if job[:at]
|
131
131
|
|
@@ -134,25 +134,32 @@ module RSpec
|
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
137
|
+
def job_match?(job)
|
138
|
+
@job ? @job == job[:job] : true
|
139
|
+
end
|
140
|
+
|
137
141
|
def arguments_match?(job)
|
138
142
|
if @args.any?
|
139
|
-
|
140
|
-
|
143
|
+
args = serialize_and_deserialize_arguments(@args)
|
144
|
+
deserialized_args = deserialize_arguments(job)
|
145
|
+
RSpec::Mocks::ArgumentListMatcher.new(*args).args_match?(*deserialized_args)
|
141
146
|
else
|
142
147
|
true
|
143
148
|
end
|
144
149
|
end
|
145
150
|
|
146
|
-
def
|
147
|
-
|
151
|
+
def queue_match?(job)
|
152
|
+
return true unless @queue
|
153
|
+
|
154
|
+
@queue == job[:queue]
|
148
155
|
end
|
149
156
|
|
150
|
-
def
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
157
|
+
def at_match?(job)
|
158
|
+
return true unless @at
|
159
|
+
return job[:at].nil? if @at == :no_wait
|
160
|
+
return false unless job[:at]
|
161
|
+
|
162
|
+
values_match?(@at, Time.at(job[:at]))
|
156
163
|
end
|
157
164
|
|
158
165
|
def set_expected_number(relativity, count)
|
@@ -165,14 +172,30 @@ module RSpec
|
|
165
172
|
end
|
166
173
|
end
|
167
174
|
|
175
|
+
def serialize_and_deserialize_arguments(args)
|
176
|
+
serialized = ::ActiveJob::Arguments.serialize(args)
|
177
|
+
::ActiveJob::Arguments.deserialize(serialized)
|
178
|
+
rescue ::ActiveJob::SerializationError
|
179
|
+
args
|
180
|
+
end
|
181
|
+
|
182
|
+
def deserialize_arguments(job)
|
183
|
+
::ActiveJob::Arguments.deserialize(job[:args])
|
184
|
+
rescue ::ActiveJob::DeserializationError
|
185
|
+
job[:args]
|
186
|
+
end
|
187
|
+
|
168
188
|
def queue_adapter
|
169
189
|
::ActiveJob::Base.queue_adapter
|
170
190
|
end
|
171
191
|
end
|
172
|
-
# rubocop: enable
|
192
|
+
# rubocop: enable Metrics/ClassLength
|
173
193
|
|
174
194
|
# @private
|
175
195
|
class HaveEnqueuedJob < Base
|
196
|
+
FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
|
197
|
+
MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
|
198
|
+
|
176
199
|
def initialize(job)
|
177
200
|
super()
|
178
201
|
@job = job
|
@@ -187,14 +210,61 @@ module RSpec
|
|
187
210
|
|
188
211
|
check(in_block_jobs)
|
189
212
|
end
|
213
|
+
|
214
|
+
def does_not_match?(proc)
|
215
|
+
set_expected_number(:at_least, 1)
|
216
|
+
|
217
|
+
!matches?(proc)
|
218
|
+
end
|
190
219
|
end
|
191
220
|
|
192
221
|
# @private
|
193
222
|
class HaveBeenEnqueued < Base
|
223
|
+
FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
|
224
|
+
MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
|
225
|
+
|
194
226
|
def matches?(job)
|
195
227
|
@job = job
|
196
228
|
check(queue_adapter.enqueued_jobs)
|
197
229
|
end
|
230
|
+
|
231
|
+
def does_not_match?(proc)
|
232
|
+
set_expected_number(:at_least, 1)
|
233
|
+
|
234
|
+
!matches?(proc)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# @private
|
239
|
+
class HavePerformedJob < Base
|
240
|
+
FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
|
241
|
+
MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
|
242
|
+
|
243
|
+
def initialize(job)
|
244
|
+
super()
|
245
|
+
@job = job
|
246
|
+
end
|
247
|
+
|
248
|
+
def matches?(proc)
|
249
|
+
raise ArgumentError, "have_performed_job only supports block expectations" unless Proc === proc
|
250
|
+
|
251
|
+
original_performed_jobs_count = queue_adapter.performed_jobs.count
|
252
|
+
proc.call
|
253
|
+
in_block_jobs = queue_adapter.performed_jobs.drop(original_performed_jobs_count)
|
254
|
+
|
255
|
+
check(in_block_jobs)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# @private
|
260
|
+
class HaveBeenPerformed < Base
|
261
|
+
FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
|
262
|
+
MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
|
263
|
+
|
264
|
+
def matches?(job)
|
265
|
+
@job = job
|
266
|
+
check(queue_adapter.performed_jobs)
|
267
|
+
end
|
198
268
|
end
|
199
269
|
end
|
200
270
|
|
@@ -232,6 +302,17 @@ module RSpec
|
|
232
302
|
# expect {
|
233
303
|
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
234
304
|
# }.to have_enqueued_job.with(42).on_queue("low").at(Date.tomorrow.noon)
|
305
|
+
#
|
306
|
+
# expect {
|
307
|
+
# HelloJob.set(queue: "low").perform_later(42)
|
308
|
+
# }.to have_enqueued_job.with(42).on_queue("low").at(:no_wait)
|
309
|
+
#
|
310
|
+
# expect {
|
311
|
+
# HelloJob.perform_later('rspec_rails', 'rails', 42)
|
312
|
+
# }.to have_enqueued_job.with { |from, to, times|
|
313
|
+
# # Perform more complex argument matching using dynamic arguments
|
314
|
+
# expect(from).to include "_#{to}"
|
315
|
+
# }
|
235
316
|
def have_enqueued_job(job = nil)
|
236
317
|
check_active_job_adapter
|
237
318
|
ActiveJob::HaveEnqueuedJob.new(job)
|
@@ -264,16 +345,93 @@ module RSpec
|
|
264
345
|
#
|
265
346
|
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
266
347
|
# expect(HelloJob).to have_been_enqueued.with(42).on_queue("low").at(Date.tomorrow.noon)
|
348
|
+
#
|
349
|
+
# HelloJob.set(queue: "low").perform_later(42)
|
350
|
+
# expect(HelloJob).to have_been_enqueued.with(42).on_queue("low").at(:no_wait)
|
267
351
|
def have_been_enqueued
|
268
352
|
check_active_job_adapter
|
269
353
|
ActiveJob::HaveBeenEnqueued.new
|
270
354
|
end
|
271
355
|
|
356
|
+
# @api public
|
357
|
+
# Passes if a job has been performed inside block. May chain at_least, at_most or exactly to specify a number of times.
|
358
|
+
#
|
359
|
+
# @example
|
360
|
+
# expect {
|
361
|
+
# perform_jobs { HeavyLiftingJob.perform_later }
|
362
|
+
# }.to have_performed_job
|
363
|
+
#
|
364
|
+
# expect {
|
365
|
+
# perform_jobs {
|
366
|
+
# HelloJob.perform_later
|
367
|
+
# HeavyLiftingJob.perform_later
|
368
|
+
# }
|
369
|
+
# }.to have_performed_job(HelloJob).exactly(:once)
|
370
|
+
#
|
371
|
+
# expect {
|
372
|
+
# perform_jobs { 3.times { HelloJob.perform_later } }
|
373
|
+
# }.to have_performed_job(HelloJob).at_least(2).times
|
374
|
+
#
|
375
|
+
# expect {
|
376
|
+
# perform_jobs { HelloJob.perform_later }
|
377
|
+
# }.to have_performed_job(HelloJob).at_most(:twice)
|
378
|
+
#
|
379
|
+
# expect {
|
380
|
+
# perform_jobs {
|
381
|
+
# HelloJob.perform_later
|
382
|
+
# HeavyLiftingJob.perform_later
|
383
|
+
# }
|
384
|
+
# }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
|
385
|
+
#
|
386
|
+
# expect {
|
387
|
+
# perform_jobs {
|
388
|
+
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
389
|
+
# }
|
390
|
+
# }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
|
391
|
+
def have_performed_job(job = nil)
|
392
|
+
check_active_job_adapter
|
393
|
+
ActiveJob::HavePerformedJob.new(job)
|
394
|
+
end
|
395
|
+
alias_method :perform_job, :have_performed_job
|
396
|
+
|
397
|
+
# @api public
|
398
|
+
# Passes if a job has been performed. May chain at_least, at_most or exactly to specify a number of times.
|
399
|
+
#
|
400
|
+
# @example
|
401
|
+
# before do
|
402
|
+
# ActiveJob::Base.queue_adapter.performed_jobs.clear
|
403
|
+
# ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
404
|
+
# ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
|
405
|
+
# end
|
406
|
+
#
|
407
|
+
# HeavyLiftingJob.perform_later
|
408
|
+
# expect(HeavyLiftingJob).to have_been_performed
|
409
|
+
#
|
410
|
+
# HelloJob.perform_later
|
411
|
+
# HeavyLiftingJob.perform_later
|
412
|
+
# expect(HeavyLiftingJob).to have_been_performed.exactly(:once)
|
413
|
+
#
|
414
|
+
# 3.times { HelloJob.perform_later }
|
415
|
+
# expect(HelloJob).to have_been_performed.at_least(2).times
|
416
|
+
#
|
417
|
+
# HelloJob.perform_later
|
418
|
+
# HeavyLiftingJob.perform_later
|
419
|
+
# expect(HelloJob).to have_been_performed
|
420
|
+
# expect(HeavyLiftingJob).to have_been_performed
|
421
|
+
#
|
422
|
+
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
423
|
+
# expect(HelloJob).to have_been_performed.with(42).on_queue("low").at(Date.tomorrow.noon)
|
424
|
+
def have_been_performed
|
425
|
+
check_active_job_adapter
|
426
|
+
ActiveJob::HaveBeenPerformed.new
|
427
|
+
end
|
428
|
+
|
272
429
|
private
|
273
430
|
|
274
431
|
# @private
|
275
432
|
def check_active_job_adapter
|
276
433
|
return if ::ActiveJob::QueueAdapters::TestAdapter === ::ActiveJob::Base.queue_adapter
|
434
|
+
|
277
435
|
raise StandardError, "To use ActiveJob matchers set `ActiveJob::Base.queue_adapter = :test`"
|
278
436
|
end
|
279
437
|
end
|