rspec-sleeping_king_studios 2.5.1 → 2.7.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.
@@ -0,0 +1,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ require 'sleeping_king_studios/tools/toolbelt'
6
+
7
+ require 'rspec/sleeping_king_studios/concerns'
8
+
9
+ module RSpec::SleepingKingStudios::Concerns
10
+ # Defines helpers for including reusable contracts in RSpec example groups.
11
+ #
12
+ # RSpec contracts are a mechanism for sharing tests between projects. For
13
+ # example, one library may define an interface or specification for a type of
14
+ # object, while a second library implements that object. By defining a
15
+ # contract and sharing that contract as part of the library, the developer
16
+ # ensures that any object that matches the contract has correctly implemented
17
+ # and conforms to the interface. This reduces duplication of tests and
18
+ # provides resiliency as an interface is developed over time and across
19
+ # versions of the library.
20
+ #
21
+ # Mechanically speaking, each contract encapsulates a section of RSpec code.
22
+ # When the contract is included in a spec, that code is then injected into the
23
+ # spec. Writing a contract, therefore, is no different than writing any other
24
+ # RSpec specification - it is only the delivery mechanism that differs. A
25
+ # contract can be any object that responds to #to_proc; the simplest contract
26
+ # is therefore a Proc or lambda that contains some RSpec code.
27
+ #
28
+ # @example Defining A Contract
29
+ # module ExampleContracts
30
+ # # This contract asserts that the object has the Enumerable module as an
31
+ # # ancestor, and that it responds to the #each method.
32
+ # SHOULD_BE_ENUMERABLE_CONTRACT = lambda do
33
+ # it 'should be Enumerable' do
34
+ # expect(subject).to be_a Enumerable
35
+ # end
36
+ #
37
+ # it 'should respond to #each' do
38
+ # expect(subject).to respond_to(:each).with(0).arguments
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ # RSpec.describe Array do
44
+ # extend RSpec::SleepingKingStudios::Concerns::IncludeContract
45
+ #
46
+ # include_contract ExampleContracts::SHOULD_BE_ENUMERABLE_CONTRACT
47
+ # end
48
+ #
49
+ # RSpec.describe Hash do
50
+ # extend RSpec::SleepingKingStudios::Concerns::IncludeContract
51
+ # include ExampleContracts
52
+ #
53
+ # include_contract 'should be enumerable'
54
+ # end
55
+ #
56
+ # @example Defining A Contract With Parameters
57
+ # module SerializerContracts
58
+ # # This contract asserts that the serialized result has the expected
59
+ # # values.
60
+ # #
61
+ # # First, we pass the contract a series of attribute names. These are
62
+ # # used to assert that the serialized attributes match the values on the
63
+ # # original object.
64
+ # #
65
+ # # Second, we pass the contract a set of attribute names and values.
66
+ # # These are used to assert that the serialized attributes have the
67
+ # # specified values.
68
+ # #
69
+ # # Finally, we can pass the contract a block, which the contract then
70
+ # # executes. Note that the block is executed in the context of our
71
+ # # describe block, and thus can take advantage of our memoized
72
+ # # #serialized helper method.
73
+ # SHOULD_SERIALIZE_ATTRIBUTES_CONTRACT = lambda \
74
+ # do |*attributes, **values, &block|
75
+ # describe '#serialize' do
76
+ # let(:serialized) { subject.serialize }
77
+ #
78
+ # it { expect(subject).to respond_to(:serialize).with(0).arguments }
79
+ #
80
+ # attributes.each do |attribute|
81
+ # it "should serialize #{attribute}" do
82
+ # expect(serialized[attribute]).to be == subject[attribute]
83
+ # end
84
+ # end
85
+ #
86
+ # values.each do |attribute, value|
87
+ # it "should serialize #{attribute}" do
88
+ # expect(serialized[attribute]).to be == value
89
+ # end
90
+ # end
91
+ #
92
+ # instance_exec(&block) if block
93
+ # end
94
+ # end
95
+ #
96
+ # RSpec.describe CaptainPicard do
97
+ # extend RSpec::SleepingKingStudios::Concerns::IncludeContract
98
+ # include SerializerContracts
99
+ #
100
+ # include_contract 'should serialize attributes',
101
+ # :name,
102
+ # :rank,
103
+ # lights: 4 do
104
+ # it 'should serialize the catchphrase' do
105
+ # expect(serialized[:catchphrase]).to be == 'Make it so.'
106
+ # end
107
+ # end
108
+ # end
109
+ #
110
+ # @see RSpec::SleepingKingStudios::Contract.
111
+ module IncludeContract
112
+ class << self
113
+ # @private
114
+ def define_contract_method(context:, contract:, name:)
115
+ method_name = +'rspec_include_contract'
116
+ method_name << '_' << tools.str.underscore(name) if contract_name?(name)
117
+ method_name << '_' << tools.str.underscore(SecureRandom.uuid)
118
+ method_name = method_name.tr(' ', '_').intern
119
+
120
+ context.define_singleton_method(method_name, &contract)
121
+
122
+ yield method_name
123
+ ensure
124
+ if context.singleton_class.respond_to?(method_name)
125
+ context.singleton_class.remove_method(method_name)
126
+ end
127
+ end
128
+
129
+ # @private
130
+ def resolve_contract(context:, contract_or_name:)
131
+ validate_contract!(contract_or_name)
132
+
133
+ return contract_or_name unless contract_name?(contract_or_name)
134
+
135
+ contract_name = contract_or_name.to_s
136
+ contract =
137
+ resolve_contract_class(context, "#{contract_name} contract") ||
138
+ resolve_contract_const(context, "#{contract_name} contract") ||
139
+ resolve_contract_class(context, contract_name) ||
140
+ resolve_contract_const(context, contract_name)
141
+
142
+ return contract if contract
143
+
144
+ raise ArgumentError, "undefined contract #{contract_or_name.inspect}"
145
+ end
146
+
147
+ private
148
+
149
+ def contract?(contract_or_name)
150
+ contract_or_name.respond_to?(:to_proc)
151
+ end
152
+
153
+ def contract_name?(contract_or_name)
154
+ contract_or_name.is_a?(String) || contract_or_name.is_a?(Symbol)
155
+ end
156
+
157
+ def resolve_contract_class(context, contract_name)
158
+ class_name = tools.str.camelize(contract_name.tr(' ', '_'))
159
+
160
+ return nil unless context.const_defined?(class_name)
161
+
162
+ context.const_get(class_name)
163
+ end
164
+
165
+ def resolve_contract_const(context, contract_name)
166
+ const_name = tools.str.underscore(contract_name.tr(' ', '_')).upcase
167
+
168
+ return nil unless context.const_defined?(const_name)
169
+
170
+ context.const_get(const_name)
171
+ end
172
+
173
+ def tools
174
+ SleepingKingStudios::Tools::Toolbelt.instance
175
+ end
176
+
177
+ def validate_contract!(contract_or_name)
178
+ raise ArgumentError, "contract can't be blank" if contract_or_name.nil?
179
+
180
+ if contract_name?(contract_or_name)
181
+ return unless contract_or_name.to_s.empty?
182
+
183
+ raise ArgumentError, "contract can't be blank"
184
+ end
185
+
186
+ return if contract?(contract_or_name)
187
+
188
+ raise ArgumentError, 'contract must be a name or respond to #to_proc'
189
+ end
190
+ end
191
+
192
+ # As #include_contract, but wraps the contract in a focused example group.
193
+ #
194
+ # @see include_contract.
195
+ def finclude_contract(contract_or_name, *arguments, **keywords, &block)
196
+ fdescribe '(focused)' do
197
+ if keywords.empty?
198
+ include_contract(contract_or_name, *arguments, &block)
199
+ else
200
+ include_contract(contract_or_name, *arguments, **keywords, &block)
201
+ end
202
+ end
203
+ end
204
+
205
+ # Adds the contract to the example group with the given parameters.
206
+ #
207
+ # @overload include_contract(contract, *arguments, **keywords, &block)
208
+ # @param contract [#to_proc] The contract to include.
209
+ # @param arguments [Array] The arguments to pass to the contract.
210
+ # @param keywords [Hash] The keywords to pass to the contract.
211
+ #
212
+ # @yield A block passed to the contract.
213
+ #
214
+ # @overload include_contract(contract_name, *arguments, **keywords, &block)
215
+ # @param contract_name [String, Symbol] The name of contract to include.
216
+ # The contract must be defined as a Class or constant in the same scope,
217
+ # e.g. include_contract('does something') expects the example group to
218
+ # define either a DoSomething class or a DO_SOMETHING constant. The name
219
+ # can optionally be suffixed with "contract", so it will also match a
220
+ # DoSomethingContract class or a DO_SOMETHING_CONTRACT constant.
221
+ # @param arguments [Array] The arguments to pass to the contract.
222
+ # @param keywords [Hash] The keywords to pass to the contract.
223
+ #
224
+ # @yield A block passed to the contract.
225
+ #
226
+ # @raise ArgumentError
227
+ def include_contract(contract_or_name, *arguments, **keywords, &block) # rubocop:disable Metrics/MethodLength
228
+ concern = RSpec::SleepingKingStudios::Concerns::IncludeContract
229
+ contract = concern.resolve_contract(
230
+ context: self,
231
+ contract_or_name: contract_or_name
232
+ )
233
+
234
+ concern.define_contract_method(
235
+ context: self,
236
+ contract: contract,
237
+ name: contract_or_name
238
+ ) do |method_name|
239
+ if keywords.empty?
240
+ send(method_name, *arguments, &block)
241
+ else
242
+ send(method_name, *arguments, **keywords, &block)
243
+ end
244
+ end
245
+ end
246
+
247
+ # As #include_contract, but wraps the contract in a skipped example group.
248
+ #
249
+ # @see include_contract.
250
+ def xinclude_contract(contract_or_name, *arguments, **keywords, &block)
251
+ xdescribe '(skipped)' do
252
+ if keywords.empty?
253
+ include_contract(contract_or_name, *arguments, &block)
254
+ else
255
+ include_contract(contract_or_name, *arguments, **keywords, &block)
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios'
4
+ require 'rspec/sleeping_king_studios/concerns/include_contract'
5
+
6
+ module RSpec::SleepingKingStudios
7
+ # A Contract wraps RSpec functionality for sharing and reusability.
8
+ #
9
+ # An RSpec::SleepingKingStudios::Contract integrates with the
10
+ # .include_contract class method to share and reuse RSpec examples and
11
+ # configuration. The major advantage a Contract object provides over using a
12
+ # Proc is documentation - tools such as YARD do not gracefully handle bare
13
+ # lambdas, while the functionality and requirements of a Contract can be
14
+ # specified using standard patterns, such as documenting the parameters passed
15
+ # to a contract using the #apply method.
16
+ #
17
+ # @example Defining A Contract
18
+ # module ExampleContracts
19
+ # # This contract asserts that the object has the Enumerable module as an
20
+ # # ancestor, and that it responds to the #each method.
21
+ # class ShouldBeEnumerableContract
22
+ # extend RSpec::SleepingKingStudios::Contract
23
+ #
24
+ # # @!method apply(example_group)
25
+ # # Adds the contract to the example group.
26
+ #
27
+ # contract do
28
+ # it 'should be Enumerable' do
29
+ # expect(subject).to be_a Enumerable
30
+ # end
31
+ #
32
+ # it 'should respond to #each' do
33
+ # expect(subject).to respond_to(:each).with(0).arguments
34
+ # end
35
+ # end
36
+ # end
37
+ # end
38
+ #
39
+ # RSpec.describe Array do
40
+ # ExampleContracts::SHOULD_BE_ENUMERABLE_CONTRACT.apply(self)
41
+ # end
42
+ #
43
+ # RSpec.describe Hash do
44
+ # extend RSpec::SleepingKingStudios::Concerns::IncludeContract
45
+ # include ExampleContracts
46
+ #
47
+ # include_contract 'should be enumerable'
48
+ # end
49
+ #
50
+ # @example Defining A Contract With Parameters
51
+ # module SerializerContracts
52
+ # # This contract asserts that the serialized result has the expected
53
+ # # values.
54
+ # #
55
+ # # First, we pass the contract a series of attribute names. These are
56
+ # # used to assert that the serialized attributes match the values on the
57
+ # # original object.
58
+ # #
59
+ # # Second, we pass the contract a set of attribute names and values.
60
+ # # These are used to assert that the serialized attributes have the
61
+ # # specified values.
62
+ # #
63
+ # # Finally, we can pass the contract a block, which the contract then
64
+ # # executes. Note that the block is executed in the context of our
65
+ # # describe block, and thus can take advantage of our memoized
66
+ # # #serialized helper method.
67
+ # class ShouldSerializeAttributesContract
68
+ # extend RSpec::SleepingKingStudios::Contract
69
+ #
70
+ # contract do |*attributes, **values, &block|
71
+ # describe '#serialize' do
72
+ # let(:serialized) { subject.serialize }
73
+ #
74
+ # it { expect(subject).to respond_to(:serialize).with(0).arguments }
75
+ #
76
+ # attributes.each do |attribute|
77
+ # it "should serialize #{attribute}" do
78
+ # expect(serialized[attribute]).to be == subject[attribute]
79
+ # end
80
+ # end
81
+ #
82
+ # values.each do |attribute, value|
83
+ # it "should serialize #{attribute}" do
84
+ # expect(serialized[attribute]).to be == value
85
+ # end
86
+ # end
87
+ #
88
+ # instance_exec(&block) if block
89
+ # end
90
+ # end
91
+ # end
92
+ #
93
+ # RSpec.describe CaptainPicard do
94
+ # SerializerContracts::ShouldSerializeAttributesContract.apply(
95
+ # self,
96
+ # :name,
97
+ # :rank,
98
+ # lights: 4) \
99
+ # do
100
+ # it 'should serialize the catchphrase' do
101
+ # expect(serialized[:catchphrase]).to be == 'Make it so.'
102
+ # end
103
+ # end
104
+ # end
105
+ #
106
+ # @see RSpec::SleepingKingStudios::Concerns::IncludeContract.
107
+ module Contract
108
+ # Adds the contract to the given example group.
109
+ #
110
+ # @param example_group [RSpec::Core::ExampleGroup] The example group to
111
+ # which the contract is applied.
112
+ # @param arguments [Array] Optional arguments to pass to the contract.
113
+ # @param keywords [Hash] Optional keywords to pass to the contract.
114
+ #
115
+ # @yield A block to pass to the contract.
116
+ #
117
+ # @see #to_proc
118
+ def apply(example_group, *arguments, **keywords, &block)
119
+ concern = RSpec::SleepingKingStudios::Concerns::IncludeContract
120
+
121
+ concern.define_contract_method(
122
+ context: example_group,
123
+ contract: self,
124
+ name: tools.str.underscore(name).gsub('::', '_')
125
+ ) do |method_name|
126
+ if keywords.empty?
127
+ example_group.send(method_name, *arguments, &block)
128
+ else
129
+ example_group.send(method_name, *arguments, **keywords, &block)
130
+ end
131
+ end
132
+ end
133
+
134
+ # @overload contract()
135
+ # @return [Proc, nil] the contract implementation for the class.
136
+ #
137
+ # @overload contract()
138
+ # Sets the contract implementation for the class.
139
+ #
140
+ # @yield [*arguments, **keywords, &block] The implementation to
141
+ # configure for the class.
142
+ #
143
+ # @yieldparam arguments [Array] Optional arguments to pass to the
144
+ # contract.
145
+ # @yieldparam keywords [Hash] Optional keywords defined for the
146
+ # contract.
147
+ # @yieldparam block [Array] A block to pass to the contract.
148
+ def contract(&block)
149
+ return @contract = block if block_given?
150
+
151
+ @contract
152
+ end
153
+
154
+ # @return [Proc, nil] the contract implementation for the class.
155
+ def to_proc
156
+ @contract
157
+ end
158
+
159
+ private
160
+
161
+ def tools
162
+ SleepingKingStudios::Tools::Toolbelt.instance
163
+ end
164
+ end
165
+ end
@@ -17,7 +17,12 @@ module RSpec::SleepingKingStudios::Matchers::BuiltIn
17
17
  # should return true if and only if the item matches the desired
18
18
  # predicate.
19
19
  def initialize *expected, &block
20
- expected << block if block_given?
20
+ if block_given?
21
+ SleepingKingStudios::Tools::CoreTools
22
+ .deprecate('IncludeMatcher with a block')
23
+
24
+ expected << block
25
+ end
21
26
 
22
27
  if expected.empty? && !allow_empty_matcher?
23
28
  raise ArgumentError,
@@ -87,18 +87,18 @@ module RSpec::SleepingKingStudios::Matchers::BuiltIn
87
87
 
88
88
  method =
89
89
  begin
90
- actual.method(method_name)
90
+ if actual.is_a?(Class) && method_name.intern == :new
91
+ actual.instance_method(:initialize)
92
+ else
93
+ actual.method(method_name)
94
+ end
91
95
  rescue NameError
92
- nil
93
- end # unless
96
+ @failing_method_reasons[method_name] = {
97
+ :is_not_a_method => true
98
+ } # end hash
94
99
 
95
- unless method.is_a?(Method)
96
- @failing_method_reasons[method_name] = {
97
- :is_not_a_method => true
98
- } # end hash
99
-
100
- next false
101
- end # unless
100
+ next false
101
+ end
102
102
 
103
103
  next true unless method_signature_expectation?
104
104
 
@@ -1,11 +1,17 @@
1
1
  # lib/rspec/sleeping_king_studios/matchers/core/alias_method.rb
2
2
 
3
- require 'rspec/sleeping_king_studios/matchers/core/alias_method_matcher'
3
+ require 'rspec/sleeping_king_studios/matchers/core/have_aliased_method_matcher'
4
4
  require 'rspec/sleeping_king_studios/matchers/macros'
5
5
 
6
6
  module RSpec::SleepingKingStudios::Matchers::Macros
7
7
  # @see RSpec::SleepingKingStudios::Matchers::Core::AliasMethodMatcher#matches?
8
8
  def alias_method expected
9
- RSpec::SleepingKingStudios::Matchers::Core::AliasMethodMatcher.new expected
9
+ SleepingKingStudios::Tools::CoreTools.deprecate(
10
+ '#alias_method',
11
+ message: 'Use #have_aliased_method instead.'
12
+ )
13
+
14
+ RSpec::SleepingKingStudios::Matchers::Core::HaveAliasedMethodMatcher
15
+ .new expected
10
16
  end # method be_boolean
11
17
  end # module
@@ -1,107 +1,22 @@
1
- # lib/rspec/sleeping_king_studios/matchers/core/alias_method_matcher.rb
1
+ # frozen_string_literal: true
2
2
 
3
- require 'rspec/sleeping_king_studios/matchers/base_matcher'
4
3
  require 'rspec/sleeping_king_studios/matchers/core'
4
+ require 'rspec/sleeping_king_studios/matchers/core/have_aliased_method_matcher'
5
5
 
6
6
  module RSpec::SleepingKingStudios::Matchers::Core
7
7
  # Matcher for testing whether an object aliases a specified method using the
8
8
  # specified other method name.
9
9
  #
10
10
  # @since 2.2.0
11
- class AliasMethodMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
12
- # @param [String, Symbol] expected The name of the method that is expected
13
- # to be aliased.
14
- def initialize expected
15
- @old_method_name = @expected = expected.intern
16
- @errors = {}
17
- end # method initialize
18
-
19
- # Specifies the name of the new method.
20
- #
21
- # @param [String, Symbol] new_method_name The method name.
22
- #
23
- # @return [AliasMethodMatcher] self
24
- def as new_method_name
25
- @new_method_name = new_method_name
26
-
27
- self
28
- end # method as
29
-
30
- # (see BaseMatcher#description)
31
- def description
32
- str = "alias :#{old_method_name}"
33
-
34
- str << " as #{new_method_name.inspect}" if new_method_name
35
-
36
- str
37
- end # method description
38
-
39
- # (see BaseMatcher#failure_message)
40
- def failure_message
41
- message = "expected #{@actual.inspect} to alias :#{old_method_name}"
42
-
43
- message << " as #{new_method_name.inspect}" if new_method_name
44
-
45
- if @errors[:does_not_respond_to_old_method]
46
- message << ", but did not respond to :#{old_method_name}"
47
-
48
- return message
49
- end # if
50
-
51
- if @errors[:does_not_respond_to_new_method]
52
- message << ", but did not respond to :#{new_method_name}"
53
-
54
- return message
55
- end # if
56
-
57
- if @errors[:does_not_alias_method]
58
- message <<
59
- ", but :#{old_method_name} and :#{new_method_name} are different "\
60
- "methods"
61
-
62
- return message
63
- end # if
64
-
65
- message
66
- end # method failure_message
67
-
11
+ class AliasMethodMatcher < RSpec::SleepingKingStudios::Matchers::Core::HaveAliasedMethodMatcher
68
12
  # (see BaseMatcher#matches?)
69
- def matches? actual
70
- super
71
-
72
- raise ArgumentError.new('must specify a new method name') if new_method_name.nil?
73
-
74
- responds_to_methods? && aliases_method?
75
- end # method matches?
76
-
77
- private
78
-
79
- attr_reader :old_method_name, :new_method_name
80
-
81
- def aliases_method?
82
- unless @actual.method(old_method_name) == @actual.method(new_method_name)
83
- @errors[:does_not_alias_method] = true
13
+ def matches?(actual)
14
+ SleepingKingStudios::Tools::CoreTools.deprecate(
15
+ 'AliasMethodMatcher',
16
+ message: 'Use a HaveAliasedMethodMatcher instead.'
17
+ )
84
18
 
85
- return false
86
- end # unless
87
-
88
- true
89
- end # method aliases_method?
90
-
91
- def responds_to_methods?
92
- unless @actual.respond_to?(old_method_name)
93
- @errors[:does_not_respond_to_old_method] = true
94
-
95
- return false
96
- end # unless
97
-
98
- unless @actual.respond_to?(new_method_name)
99
- @errors[:does_not_respond_to_new_method] = true
100
-
101
- return false
102
- end # unless
103
-
104
- true
105
- end # method responds_to_methods?
106
- end # class
107
- end # module
19
+ super
20
+ end
21
+ end
22
+ end
@@ -125,6 +125,15 @@ module RSpec::SleepingKingStudios::Matchers::Core
125
125
 
126
126
  # (see BaseMatcher#matches?)
127
127
  def matches? actual
128
+ # :nocov:
129
+ if RUBY_VERSION < '3.0'
130
+ SleepingKingStudios::Tools::CoreTools.deprecate('DelegateMethodMatcher')
131
+ else
132
+ SleepingKingStudios::Tools::CoreTools
133
+ .new(deprecation_strategy: 'raise')
134
+ .deprecate('DelegateMethodMatcher')
135
+ end
136
+
128
137
  super
129
138
 
130
139
  raise ArgumentError.new('must specify a target') if @target.nil?
@@ -187,14 +196,22 @@ module RSpec::SleepingKingStudios::Matchers::Core
187
196
 
188
197
  private
189
198
 
190
- def call_method arguments, expected_return = DEFAULT_EXPECTED_RETURN
199
+ def call_method(arguments:, keywords:, expected_return: DEFAULT_EXPECTED_RETURN)
191
200
  if @expected_block
192
201
  @received_block = false
193
202
  block = ->(*args, **kwargs, &block) {}
194
203
 
195
- return_value = @actual.send(@expected, *arguments, &block)
204
+ if keywords.empty?
205
+ return_value = @actual.send(@expected, *arguments, &block)
206
+ else
207
+ return_value = @actual.send(@expected, *arguments, **keywords, &block)
208
+ end
196
209
  else
197
- return_value = @actual.send(@expected, *arguments)
210
+ if keywords.empty?
211
+ return_value = @actual.send(@expected, *arguments)
212
+ else
213
+ return_value = @actual.send(@expected, *arguments, **keywords)
214
+ end
198
215
  end
199
216
 
200
217
  @received_return_values << return_value
@@ -215,19 +232,22 @@ module RSpec::SleepingKingStudios::Matchers::Core
215
232
  def delegates_method?
216
233
  stub_target!
217
234
 
218
- args = @expected_arguments.dup
219
- args << @expected_keywords unless @expected_keywords.empty?
220
-
221
235
  if @expected_return_values.empty?
222
- call_method(args)
236
+ call_method(arguments: @expected_arguments, keywords: @expected_keywords)
223
237
  else
224
238
  @expected_return_values.each do |return_value|
225
- call_method(args, return_value)
239
+ call_method(arguments: @expected_arguments, keywords: @expected_keywords, expected_return: return_value)
226
240
  end # each
227
241
  end # if-else
228
242
 
229
243
  matcher = RSpec::Mocks::Matchers::HaveReceived.new(@expected)
230
- matcher = matcher.with(*args) unless args.empty?
244
+ if !@expected_arguments.empty? && !@expected_keywords.empty?
245
+ matcher = matcher.with(*@expected_arguments, **@expected_keywords)
246
+ elsif !@expected_arguments.empty?
247
+ matcher = matcher.with(*@expected_arguments)
248
+ elsif !@expected_keywords.empty?
249
+ matcher = matcher.with(**@expected_keywords)
250
+ end
231
251
 
232
252
  unless @expected_return_values.empty?
233
253
  matcher = matcher.exactly(@expected_return_values.count).times
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/core/have_aliased_method_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
5
+
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
7
+ # @see RSpec::SleepingKingStudios::Matchers::Core::HaveAliasedMethodMatcher#matches?
8
+ def have_aliased_method(original_name)
9
+ RSpec::SleepingKingStudios::Matchers::Core::HaveAliasedMethodMatcher
10
+ .new(original_name)
11
+ end
12
+ end