flexmock 2.2.1 → 2.3.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ac25b7c70e3a49d1404279263c5ad6e5cfb19128
4
- data.tar.gz: 41c3cc477ad1623060a73eacd7dea095226ad6c3
2
+ SHA256:
3
+ metadata.gz: 55dadc89381c542f0d6f81c9484b4febc6adf1ef2b8292a504d60406e4c6f915
4
+ data.tar.gz: 80eb120f913a93d90f855442ab0bf8fc13bbec222279dec77340979a8cc8f024
5
5
  SHA512:
6
- metadata.gz: 486419f39b00d0f8545671d597b7e7832a804545ee40d78bec76a6e46edc68632f16465a7745d0eb4b5bf94fc4612b753134c2c756bdf27ed1c477b71622d827
7
- data.tar.gz: 0bc257bddd171c211d20c1424626a67487387094a43e0e57dd8a7bf49b3923caf4f4947c73ce44e33c98ae7b6443879aba603b44749439d39b8caa38ac9d31f9
6
+ metadata.gz: 8afcf774571ed28b0e90b631ebfb88e91812c9494f2bef4c3b1d36dc4726514c7fb21ff22f6c9accd5331f214a5f5f12826b5d1d07a6868fb39a6154d016a0f7
7
+ data.tar.gz: 7b3bf8f227f97efb5e4a6cb8d80d9e5ef4a2c1f29e8978fadb95597b419010f1abeaf8650bdbc533389fd09746aeae597fc97447ec2b7e46507f36cff180dc58
@@ -0,0 +1,32 @@
1
+ name: Unit Tests
2
+
3
+ on: [pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+
9
+ strategy:
10
+ matrix:
11
+ ruby-version: ["2.7", "2.6", "2.5"]
12
+
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Set up Ruby ${{ matrix.ruby-version }}
16
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
17
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
18
+ # uses: ruby/setup-ruby@v1
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby-version }}
22
+ - name: Configure bundler without 'vscode'
23
+ run: bundle config set --local without vscode
24
+ - name: Install dependencies
25
+ run: bundle install
26
+ - name: Run tests
27
+ run: bundle exec rake test
28
+ env:
29
+ GIT_AUTHOR_NAME: flexmock CI Git Identity
30
+ GIT_AUTHOR_EMAIL: flexmock@github.actions
31
+ GIT_COMMITTER_NAME: flexmock CI Git Identity
32
+ GIT_COMMITTER_EMAIL: flexmock@github.actions
data/.travis.yml CHANGED
@@ -1,10 +1,31 @@
1
1
  language: ruby
2
2
  rvm:
3
- - '2.0'
4
- - '2.1'
5
- - '2.2'
6
- - 2.3.1
7
- - jruby-9.1.2.0
3
+ - 2.2
4
+ - 2.3
5
+ - 2.4
6
+ - 2.5
7
+ - 2.6
8
+ - 2.7
9
+ - ruby-head
10
+
11
+ matrix:
12
+ include:
13
+ - rvm: jruby-9.1.17.0
14
+ jdk: openjdk8
15
+ name: "JRuby 9.1"
16
+ - rvm: jruby-9.2.11.1
17
+ jdk: openjdk11
18
+ name: "JRuby 9.2"
19
+
20
+ allow_failures:
21
+ - rvm: ruby-head
22
+ # Revisit when fixing https://github.com/jruby/jruby/issues/4678
23
+ - name: "JRuby 9.2"
24
+ fast_finish: true
25
+
26
+ env:
27
+ global:
28
+ - JRUBY_OPTS="--debug"
8
29
 
9
30
  script:
10
31
  - rake test
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Sylvain Joyeux
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Flex Mock -- Making Mocking Easy
2
2
 
3
3
  [![Build Status](https://travis-ci.org/doudou/flexmock.svg?branch=master)](https://travis-ci.org/doudou/flexmock)
4
- [![Gem Version](https://badge.fury.io/rb/flexmock.svg)](http://badge.fury.io/rb/flexmock)
4
+ [![Gem Version](https://badge.fury.io/rb/flexmock.svg)](https://badge.fury.io/rb/flexmock)
5
5
  [![Coverage Status](https://coveralls.io/repos/doudou/flexmock/badge.svg?branch=master&service=github)](https://coveralls.io/github/doudou/flexmock?branch=master)
6
- [![Documentation](http://b.repl.ca/v1/yard-docs-blue.png)](http://rubydoc.info/gems/flexmock/frames)
6
+ [![API Documentation](http://img.shields.io/badge/yard-docs-blue.svg)](https://rubydoc.info/gems/flexmock/frames)
7
7
 
8
8
  FlexMock is a simple, but flexible, mock object library for Ruby unit
9
9
  testing.
@@ -22,6 +22,13 @@ Only significant changes (new APIs, deprecated APIs or backward-compatible
22
22
  changes) are documented here, a.k.a. minor or major version bumps. If you want a
23
23
  detailed changelog, go over the commit log in github (it's pretty low-traffic)
24
24
 
25
+ 2.3.0:
26
+ - implemented validation of call arity for partial mocks. By setting
27
+ FlexMock.partials_verify_signatures = true
28
+ flexmock will verify on partials that the number of arguments, and the
29
+ keyword arguments passed to the mocked call match the existing method's
30
+ signature
31
+
25
32
  2.2.0:
26
33
 
27
34
  - #new_instances now mocks the #initialize method instead of mocking after the
@@ -938,7 +945,7 @@ MiniTest for asserting that mocked methods are actually called.
938
945
  additional validations on supplied arguments. Default is no
939
946
  additional validations.
940
947
 
941
- * <b>on: <em>n</em>
948
+ * <b>on: <em>n</em></b>
942
949
 
943
950
  Only apply the additional validations on the <em>n</em>'th
944
951
  invocation of the matching method. Default is apply additional
data/doc/index.rdoc CHANGED
@@ -4,20 +4,20 @@ FlexMock is a simple, but flexible, mock object library for Ruby unit
4
4
  testing.
5
5
 
6
6
  This is the API documentation site for flexmock. You can find the user
7
- documentation at http://github.com/jimweirich/flexmock
7
+ documentation at http://github.com/doudou/flexmock
8
8
 
9
9
  == Links
10
10
 
11
- * <b>User Guide</b> -- http://github.com/jimweirich/flexmock
12
- * <b>API Documents</b> -- http://flexmock.rubyforge.org
11
+ * <b>User Guide</b> -- http://github.com/doudou/flexmock
13
12
  * <b>RubyGems</b> -- Install with: `gem install flexmock`
14
- * <b>Source</b> -- https://github.com/jimweirich/flexmock
15
- * <b>Bug Reports / Issue Tracking</b> -- https://github.com/jimweirich/flexmock/issues
16
- * <b>Continuous Integration</b> -- http://travis-ci.org/#!/jimweirich/flexmock
13
+ * <b>Source</b> -- https://github.com/doudou/flexmock
14
+ * <b>Bug Reports / Issue Tracking</b> -- https://github.com/doudou/flexmock/issues
15
+ * <b>Continuous Integration</b> -- http://travis-ci.org/#!/doudou/flexmock
17
16
 
18
17
  == License
19
18
 
20
19
  Copyright 2003-2013 by Jim Weirich (jim.weirich@gmail.com).
20
+ Copyright 2013-2020 by Sylvain Joyeux (sylvain.joyeux@m4x.org)
21
21
  All rights reserved.
22
22
 
23
23
  Permission is granted for use, copying, modification, distribution,
data/flexmock.gemspec CHANGED
@@ -10,13 +10,13 @@ spec = Gem::Specification.new do |s|
10
10
  interface is simple, it is very flexible.
11
11
  }
12
12
 
13
- s.required_ruby_version = ">= 2.0"
13
+ s.required_ruby_version = ">= 2.2"
14
14
 
15
15
  s.license = 'MIT'
16
16
 
17
17
  #### Dependencies and requirements.
18
18
 
19
- s.add_development_dependency 'minitest'
19
+ s.add_development_dependency 'minitest', ">= 5.0"
20
20
  s.add_development_dependency 'rake'
21
21
  s.add_development_dependency 'simplecov', '>= 0.11.0'
22
22
  s.add_development_dependency 'coveralls'
File without changes
File without changes
data/lib/flexmock/base.rb CHANGED
File without changes
data/lib/flexmock/core.rb CHANGED
@@ -45,28 +45,59 @@ class FlexMock
45
45
  include Ordering
46
46
 
47
47
  attr_reader :flexmock_name
48
- attr_accessor :flexmock_container
48
+ attr_reader :flexmock_container_stack
49
49
 
50
50
  class << self
51
51
  attr_accessor :partials_are_based
52
+ attr_accessor :partials_verify_signatures
52
53
  end
53
54
  self.partials_are_based = false
55
+ self.partials_verify_signatures = false
56
+
57
+ # Null object for {#parent_mock}
58
+ class NullParentMock
59
+ def flexmock_expectations_for(sym)
60
+ end
61
+ end
54
62
 
55
63
  # Create a FlexMock object with the given name. The name is used in
56
64
  # error messages. If no container is given, create a new, one-off
57
65
  # container for this mock.
58
- def initialize(name="unknown", container=nil)
66
+ def initialize(name="unknown", container=nil, parent: nil)
59
67
  @flexmock_name = name
60
68
  @flexmock_closed = false
69
+ @flexmock_container_stack = Array.new
61
70
  @expectations = Hash.new
62
- @ignore_missing = false
63
71
  @verified = false
64
72
  @calls = []
65
73
  @base_class = nil
74
+ if parent
75
+ @ignore_missing = parent.ignore_missing?
76
+ @parent_mock = parent
77
+ else
78
+ @ignore_missing = false
79
+ @parent_mock = NullParentMock.new
80
+ end
66
81
  container = UseContainer.new if container.nil?
67
82
  container.flexmock_remember(self)
68
83
  end
69
84
 
85
+ def ignore_missing?
86
+ @ignore_missing
87
+ end
88
+
89
+ def flexmock_container
90
+ flexmock_container_stack.last
91
+ end
92
+
93
+ def push_flexmock_container(container)
94
+ flexmock_container_stack.push(container)
95
+ end
96
+
97
+ def pop_flexmock_container
98
+ flexmock_container_stack.pop
99
+ end
100
+
70
101
  # Return the inspection string for a mock.
71
102
  def inspect
72
103
  "<FlexMock:#{flexmock_name}>"
@@ -115,8 +146,8 @@ class FlexMock
115
146
  flexmock_wrap do
116
147
  if flexmock_closed?
117
148
  FlexMock.undefined
118
- elsif handler = @expectations[sym]
119
- handler.call(enhanced_args, call_record)
149
+ elsif exp = flexmock_expectations_for(sym)
150
+ exp.call(enhanced_args, call_record)
120
151
  elsif @base_class && @base_class.flexmock_defined?(sym)
121
152
  FlexMock.undefined
122
153
  elsif @ignore_missing
@@ -137,13 +168,14 @@ class FlexMock
137
168
 
138
169
  # Find the mock expectation for method sym and arguments.
139
170
  def flexmock_find_expectation(method_name, *args) # :nodoc:
140
- exp = @expectations[method_name]
141
- exp ? exp.find_expectation(*args) : nil
171
+ if exp = flexmock_expectations_for(method_name)
172
+ exp.find_expectation(*args)
173
+ end
142
174
  end
143
175
 
144
176
  # Return the expectation director for a method name.
145
177
  def flexmock_expectations_for(method_name) # :nodoc:
146
- @expectations[method_name]
178
+ @expectations[method_name] || @parent_mock.flexmock_expectations_for(method_name)
147
179
  end
148
180
 
149
181
  def flexmock_base_class
@@ -153,8 +185,10 @@ class FlexMock
153
185
  def flexmock_based_on(base_class)
154
186
  @base_class = base_class
155
187
  if base_class <= Kernel
156
- should_receive(:class => base_class)
157
- should_receive(:kind_of?).and_return { |against| base_class <= against }
188
+ if self.class != base_class
189
+ should_receive(:class => base_class)
190
+ should_receive(:kind_of?).and_return { |against| base_class <= against }
191
+ end
158
192
  end
159
193
  end
160
194
 
@@ -179,9 +213,9 @@ class FlexMock
179
213
 
180
214
  # Override the built-in +method+ to include the mocked methods.
181
215
  def method(method_name)
182
- @expectations[method_name] || super
216
+ flexmock_expectations_for(method_name) || super
183
217
  rescue NameError => ex
184
- if @ignore_missing
218
+ if ignore_missing?
185
219
  proc { FlexMock.undefined }
186
220
  else
187
221
  raise ex
@@ -211,15 +245,22 @@ class FlexMock
211
245
  flexmock_define_expectation(caller, *args)
212
246
  end
213
247
 
248
+ ON_RUBY_20 = (RUBY_VERSION =~ /^2\.0\./)
249
+
214
250
  # Using +location+, define the expectations specified by +args+.
215
251
  def flexmock_define_expectation(location, *args)
216
252
  @last_expectation = EXP_BUILDER.parse_should_args(self, args) do |method_name|
217
- @expectations[method_name] ||= ExpectationDirector.new(method_name)
253
+ exp = flexmock_expectations_for(method_name) || ExpectationDirector.new(method_name)
254
+ @expectations[method_name] = exp
218
255
  result = Expectation.new(self, method_name, location)
219
- @expectations[method_name] << result
220
- override_existing_method(method_name) if flexmock_respond_to?(method_name)
221
- result = ExplicitNeeded.new(result, method_name, @base_class) if
222
- @base_class && ! @base_class.flexmock_defined?(method_name)
256
+ exp << result
257
+ override_existing_method(method_name) if flexmock_respond_to?(method_name, true)
258
+
259
+ if @base_class && !@base_class.flexmock_defined?(method_name)
260
+ if !ON_RUBY_20 || !@base_class.ancestors.include?(Class)
261
+ result = ExplicitNeeded.new(result, method_name, @base_class)
262
+ end
263
+ end
223
264
  result
224
265
  end
225
266
  end
File without changes
File without changes
@@ -38,6 +38,7 @@ class FlexMock
38
38
  @location = location
39
39
  @expected_args = nil
40
40
  @count_validators = []
41
+ @signature_validator = SignatureValidator.new(self)
41
42
  @count_validator_class = ExactCountValidator
42
43
  @actual_count = 0
43
44
  @return_value = nil
@@ -65,6 +66,9 @@ class FlexMock
65
66
  @count_validators.each do |validator|
66
67
  result << validator.describe
67
68
  end
69
+ if !@signature_validator.null?
70
+ result << @signature_validator.describe
71
+ end
68
72
  result
69
73
  end
70
74
 
@@ -79,11 +83,18 @@ class FlexMock
79
83
  FlexMock.framework_adapter.check(e.message) { false }
80
84
  end
81
85
 
86
+ def validate_signature(args)
87
+ @signature_validator.validate(args)
88
+ rescue SignatureValidator::ValidationFailed => e
89
+ FlexMock.framework_adapter.check(e.message) { false }
90
+ end
91
+
82
92
  # Verify the current call with the given arguments matches the
83
93
  # expectations recorded in this object.
84
94
  def verify_call(*args)
85
95
  validate_eligible
86
96
  validate_order
97
+ validate_signature(args)
87
98
  @actual_count += 1
88
99
  perform_yielding(args)
89
100
  return_value(args)
@@ -132,11 +143,6 @@ class FlexMock
132
143
  @count_validators.all? { |v| v.eligible?(@actual_count) }
133
144
  end
134
145
 
135
- # Is this expectation constrained by any call counts?
136
- def call_count_constrained?
137
- ! @count_validators.empty?
138
- end
139
-
140
146
  # Validate that the order
141
147
  def validate_order
142
148
  if @order_number
@@ -186,6 +192,28 @@ class FlexMock
186
192
  self
187
193
  end
188
194
 
195
+ # Validate general parameters on the call signature
196
+ def with_signature(
197
+ required_arguments: 0, optional_arguments: 0, splat: false,
198
+ required_keyword_arguments: [], optional_keyword_arguments: [], keyword_splat: false)
199
+ @signature_validator = SignatureValidator.new(
200
+ self,
201
+ required_arguments: required_arguments,
202
+ optional_arguments: optional_arguments,
203
+ splat: splat,
204
+ required_keyword_arguments: required_keyword_arguments,
205
+ optional_keyword_arguments: optional_keyword_arguments,
206
+ keyword_splat: keyword_splat)
207
+ self
208
+ end
209
+
210
+ # Validate that the passed arguments match the method signature from the
211
+ # given instance method
212
+ def with_signature_matching(instance_method)
213
+ @signature_validator = SignatureValidator.from_instance_method(self, instance_method)
214
+ self
215
+ end
216
+
189
217
  # :call-seq:
190
218
  # and_return(value)
191
219
  # and_return(value, value, ...)
@@ -55,7 +55,7 @@ class FlexMock
55
55
  elsif names.size > 1
56
56
  create_demeter_chain(mock, names)
57
57
  else
58
- fail "Empty list of names"
58
+ fail ArgumentError, "Empty list of names"
59
59
  end
60
60
  end
61
61
 
@@ -40,7 +40,9 @@ class FlexMock
40
40
  call_record.expectation = exp if call_record
41
41
  FlexMock.check(
42
42
  proc { "no matching handler found for " +
43
- FlexMock.format_call(@sym, args) }
43
+ FlexMock.format_call(@sym, args) +
44
+ "\nDefined expectations:\n " +
45
+ @expectations.map(&:description).join("\n ") }
44
46
  ) { !exp.nil? }
45
47
  returned_value = exp.verify_call(*args)
46
48
  returned_value
@@ -29,8 +29,10 @@ class FlexMock
29
29
  @expectation.mock = m
30
30
  end
31
31
 
32
+ WHITELIST = [:with_signature_matching]
33
+
32
34
  def method_missing(sym, *args, &block)
33
- if explicit?
35
+ if explicit? || WHITELIST.include?(sym)
34
36
  @expectation.send(sym, *args, &block)
35
37
  else
36
38
  fail NoMethodError, "Cannot stub methods not defined by the base class\n" +
File without changes
@@ -98,7 +98,11 @@ class FlexMock
98
98
  end
99
99
 
100
100
  def assertion_failed_error
101
- MiniTest::Assertion
101
+ if defined?(::Minitest)
102
+ ::Minitest::Assertion
103
+ else
104
+ MiniTest::Assertion
105
+ end
102
106
  end
103
107
 
104
108
  def check_failed_error
@@ -52,6 +52,7 @@ class FlexMock
52
52
  def flexmock_close
53
53
  flexmock_created_mocks.each do |m|
54
54
  m.flexmock_teardown
55
+ m.pop_flexmock_container
55
56
  end
56
57
  @flexmock_created_mocks = []
57
58
  end
@@ -127,7 +128,7 @@ class FlexMock
127
128
  def flexmock_remember(mocking_object)
128
129
  @flexmock_created_mocks ||= []
129
130
  @flexmock_created_mocks << mocking_object
130
- mocking_object.flexmock_container = self
131
+ mocking_object.push_flexmock_container(self)
131
132
  mocking_object
132
133
  end
133
134
 
data/lib/flexmock/noop.rb CHANGED
File without changes
@@ -28,47 +28,85 @@ class FlexMock
28
28
 
29
29
  attr_reader :mock
30
30
 
31
- ProxyBox = Struct.new :proxy
31
+ # Boxing of the flexmock proxy
32
+ #
33
+ # It is managed as a stack in order to allow to setup containers recursively
34
+ # (as e.g. FlexMock.use( ... ) checks)
35
+ class ProxyBox
36
+ attr_reader :stack
37
+
38
+ Element = Struct.new :proxy, :container
39
+
40
+ def initialize
41
+ @stack = [Element.new]
42
+ end
43
+
44
+ # Tests whether the given container is the one on which the current proxy
45
+ # acts
46
+ def container
47
+ stack.last.container
48
+ end
49
+
50
+ def proxy
51
+ stack.last.proxy
52
+ end
53
+
54
+ def push(proxy, container)
55
+ stack.push(Element.new(proxy, container))
56
+ end
57
+
58
+ def pop
59
+ if !stack.empty?
60
+ stack.pop
61
+ end
62
+ end
63
+
64
+ def empty?
65
+ stack.size == 1
66
+ end
67
+ end
32
68
 
33
69
  # Make a partial mock proxy and install it on the target +obj+.
34
70
  def self.make_proxy_for(obj, container, name, safe_mode)
35
71
  name ||= "flexmock(#{obj.class.to_s})"
36
- if ! proxy_defined_on?(obj)
37
- mock = FlexMock.new(name, container)
38
- proxy = PartialMockProxy.new(obj, mock, safe_mode)
39
- if obj.instance_variable_defined?("@flexmock_proxy")
40
- obj.instance_variable_get("@flexmock_proxy").proxy = proxy
41
- else
42
- obj.instance_variable_set("@flexmock_proxy", ProxyBox.new(proxy))
43
- end
72
+ if !obj.instance_variable_defined?("@flexmock_proxy")
73
+ proxy_box = obj.instance_variable_set("@flexmock_proxy", ProxyBox.new)
74
+ else
75
+ proxy_box = obj.instance_variable_get("@flexmock_proxy")
44
76
  end
45
- obj.instance_variable_get("@flexmock_proxy").proxy
46
- end
47
77
 
48
- # Is there a mock proxy defined on the domain object?
49
- def self.proxy_defined_on?(obj)
50
- obj.instance_variable_defined?("@flexmock_proxy") &&
51
- obj.instance_variable_get("@flexmock_proxy").proxy
78
+ if proxy_box.container != container
79
+ if !proxy_box.empty?
80
+ parent_proxy, _ = proxy_box.proxy
81
+ parent_mock = parent_proxy.mock
82
+ end
83
+
84
+ mock = FlexMock.new(name, container, parent: parent_mock)
85
+ proxy = PartialMockProxy.new(obj, mock, safe_mode, parent: parent_proxy)
86
+ proxy_box.push(proxy, container)
87
+ end
88
+ proxy_box.proxy
52
89
  end
53
90
 
54
91
  # The following methods are added to partial mocks so that they
55
92
  # can act like a mock.
56
93
 
57
94
  MOCK_METHODS = [
58
- :should_receive, :new_instances,
95
+ :should_receive, :new_instances, :should_expect,
59
96
  :should_receive_with_location,
60
97
  :flexmock_get, :flexmock_teardown, :flexmock_verify,
61
- :flexmock_received?, :flexmock_calls, :flexmock_find_expectation
98
+ :flexmock_received?, :flexmock_calls, :flexmock_find_expectation,
99
+ :invoke_original
62
100
  ]
63
101
 
64
102
  # Initialize a PartialMockProxy object.
65
- def initialize(obj, mock, safe_mode)
103
+ def initialize(obj, mock, safe_mode, parent: nil)
66
104
  @obj = obj
67
105
  @mock = mock
68
- @method_definitions = {}
69
- @methods_proxied = []
70
106
  @proxy_definition_module = nil
107
+ @parent = parent
71
108
  @initialize_override = nil
109
+
72
110
  unless safe_mode
73
111
  add_mock_method(:should_receive)
74
112
  MOCK_METHODS.each do |sym|
@@ -84,6 +122,14 @@ class FlexMock
84
122
  @mock
85
123
  end
86
124
 
125
+ def push_flexmock_container(container)
126
+ @mock.push_flexmock_container(container)
127
+ end
128
+
129
+ def pop_flexmock_container
130
+ @mock.pop_flexmock_container
131
+ end
132
+
87
133
  # :call-seq:
88
134
  # should_receive(:method_name)
89
135
  # should_receive(:method1, method2, ...)
@@ -108,12 +154,66 @@ class FlexMock
108
154
  flexmock_define_expectation(caller, *args)
109
155
  end
110
156
 
157
+ def should_expect(*args)
158
+ yield Recorder.new(self)
159
+ end
160
+
161
+ # Invoke the original of a mocked method
162
+ #
163
+ # Usually called in a #and_return statement
164
+ def invoke_original(m, *args, &block)
165
+ if block
166
+ args << block
167
+ end
168
+ flexmock_invoke_original(m, args)
169
+ end
170
+
171
+ # Whether the given method's original definition has been stored
172
+ def find_original_method(m)
173
+ it = @obj.method(m)
174
+ while it && (it.owner != @proxy_definition_module)
175
+ it = it.super_method
176
+ end
177
+
178
+ return unless it
179
+ while it && it.owner.kind_of?(ProxyDefinitionModule)
180
+ it = it.super_method
181
+ end
182
+ it
183
+ rescue NameError => e
184
+ raise unless e.name == m
185
+ end
186
+
187
+ # Whether the given method's original definition has been stored
188
+ def original_method(m)
189
+ unless (m = find_original_method(m))
190
+ raise ArgumentError, "no original method for #{m}"
191
+ end
192
+ m
193
+ end
194
+
195
+ # Whether the given method's original definition has been stored
196
+ def has_original_method?(m)
197
+ find_original_method(m)
198
+ end
199
+
200
+ # Whether the given method is already being proxied
201
+ def has_proxied_method?(m)
202
+ @proxy_definition_module &&
203
+ @proxy_definition_module.method_defined?(m)
204
+ end
205
+
111
206
  def flexmock_define_expectation(location, *args)
112
207
  EXP_BUILDER.parse_should_args(self, args) do |method_name|
113
- unless @methods_proxied.include?(method_name)
114
- hide_existing_method(method_name)
208
+ if !has_proxied_method?(method_name)
209
+ define_proxy_method(method_name)
115
210
  end
116
211
  ex = @mock.flexmock_define_expectation(location, method_name)
212
+ if FlexMock.partials_verify_signatures
213
+ if (existing_method = find_original_method(method_name))
214
+ ex.with_signature_matching(existing_method)
215
+ end
216
+ end
117
217
  ex.mock = self
118
218
  ex
119
219
  end
@@ -124,7 +224,6 @@ class FlexMock
124
224
  end
125
225
 
126
226
  def add_mock_method(method_name)
127
- stow_existing_definition(method_name)
128
227
  proxy_module_eval do
129
228
  define_method(method_name) { |*args, &block|
130
229
  proxy = __flexmock_proxy or
@@ -233,12 +332,12 @@ class FlexMock
233
332
  # Invoke the original definition of method on the object supported by
234
333
  # the stub.
235
334
  def flexmock_invoke_original(method, args)
236
- if original_method = @method_definitions[method]
335
+ if (original_method = find_original_method(method))
237
336
  if Proc === args.last
238
337
  block = args.last
239
338
  args = args[0..-2]
240
339
  end
241
- original_method.bind(@obj).call(*args, &block)
340
+ original_method.call(*args, &block)
242
341
  else
243
342
  @obj.__send__(:method_missing, method, *args, &block)
244
343
  end
@@ -262,7 +361,7 @@ class FlexMock
262
361
  end
263
362
  if @obj.instance_variable_defined?(:@flexmock_proxy) &&
264
363
  (box = @obj.instance_variable_get(:@flexmock_proxy))
265
- box.proxy = nil
364
+ box.pop
266
365
  end
267
366
  @obj = nil
268
367
  end
@@ -311,11 +410,14 @@ class FlexMock
311
410
  target_singleton_class.class_eval(*args, &block)
312
411
  end
313
412
 
413
+ class ProxyDefinitionModule < Module
414
+ end
415
+
314
416
  # Evaluate a block into the module we use to define the proxy methods
315
417
  def proxy_module_eval(*args, &block)
316
418
  if !@proxy_definition_module
317
419
  obj = @obj
318
- @proxy_definition_module = m = Module.new do
420
+ @proxy_definition_module = m = ProxyDefinitionModule.new do
319
421
  define_method("__flexmock_proxy") do
320
422
  if box = obj.instance_variable_get(:@flexmock_proxy)
321
423
  box.proxy
@@ -334,20 +436,9 @@ class FlexMock
334
436
  # not a singleton, all we need to do is override it with our own
335
437
  # singleton.
336
438
  def hide_existing_method(method_name)
337
- stow_existing_definition(method_name)
338
439
  define_proxy_method(method_name)
339
440
  end
340
441
 
341
- # Stow the existing method definition so that it can be recovered
342
- # later.
343
- def stow_existing_definition(method_name)
344
- if !@methods_proxied.include?(method_name)
345
- @method_definitions[method_name] = target_class_eval { instance_method(method_name) }
346
- @methods_proxied << method_name
347
- end
348
- rescue NameError
349
- end
350
-
351
442
  # Define a proxy method that forwards to our mock object. The
352
443
  # proxy method is defined as a singleton method on the object
353
444
  # being mocked.
@@ -372,6 +463,5 @@ class FlexMock
372
463
  def detached?
373
464
  @obj.nil?
374
465
  end
375
-
376
466
  end
377
467
  end
File without changes
File without changes
File without changes
@@ -51,7 +51,7 @@ class FlexMock
51
51
  @assertions = 0
52
52
  end
53
53
 
54
- def make_assertion(msg, &block)
54
+ def make_assertion(msg, backtrace = caller, &block)
55
55
  unless yield
56
56
  msg = msg.call if msg.is_a?(Proc)
57
57
  assert(false, msg)
@@ -68,7 +68,7 @@ class FlexMock
68
68
  def assertion_failed_error
69
69
  defined?(Test::Unit::AssertionFailedError) ?
70
70
  Test::Unit::AssertionFailedError :
71
- MiniTest::Assertion
71
+ Minitest::Assertion
72
72
  end
73
73
 
74
74
  def check_failed_error
@@ -30,8 +30,8 @@ class FlexMock
30
30
  end
31
31
  end
32
32
 
33
- if defined?(MiniTest)
34
- module MiniTest
33
+ if defined?(Minitest)
34
+ module Minitest
35
35
  class Unit
36
36
  class TestCase
37
37
  FlexMock::GenericTestCase.define_extensions_on(self)
@@ -9,6 +9,7 @@
9
9
  # above copyright notice is included.
10
10
  #+++
11
11
 
12
+ require 'set'
12
13
  require 'flexmock/noop'
13
14
  require 'flexmock/spy_describers'
14
15
 
@@ -136,4 +137,180 @@ class FlexMock
136
137
  "At most #{@limit}"
137
138
  end
138
139
  end
140
+
141
+ # Validate that the call matches a given signature
142
+ #
143
+ # The validator created by {#initialize} matches any method call
144
+ class SignatureValidator
145
+ class ValidationFailed < RuntimeError
146
+ end
147
+
148
+ # The number of required arguments
149
+ attr_reader :required_arguments
150
+ # The number of optional arguments
151
+ attr_reader :optional_arguments
152
+ # Whether there is a positional argument splat
153
+ def splat?
154
+ @splat
155
+ end
156
+ # The names of required keyword arguments
157
+ # @return [Set<Symbol>]
158
+ attr_reader :required_keyword_arguments
159
+ # The names of optional keyword arguments
160
+ # @return [Set<Symbol>]
161
+ attr_reader :optional_keyword_arguments
162
+ # Whether there is a splat for keyword arguments (double-star)
163
+ def keyword_splat?
164
+ @keyword_splat
165
+ end
166
+
167
+ # Whether this method may have keyword arguments
168
+ def expects_keyword_arguments?
169
+ keyword_splat? || !required_keyword_arguments.empty? || !optional_keyword_arguments.empty?
170
+ end
171
+
172
+ # Whether this method may have keyword arguments
173
+ def requires_keyword_arguments?
174
+ !required_keyword_arguments.empty?
175
+ end
176
+
177
+ def initialize(
178
+ expectation,
179
+ required_arguments: 0,
180
+ optional_arguments: 0,
181
+ splat: true,
182
+ required_keyword_arguments: [],
183
+ optional_keyword_arguments: [],
184
+ keyword_splat: true)
185
+ @exp = expectation
186
+ @required_arguments = required_arguments
187
+ @optional_arguments = optional_arguments
188
+ @required_keyword_arguments = required_keyword_arguments.to_set
189
+ @optional_keyword_arguments = optional_keyword_arguments.to_set
190
+ @splat = splat
191
+ @keyword_splat = keyword_splat
192
+ end
193
+
194
+ # Whether this tests anything
195
+ #
196
+ # It will return if this validator would validate any set of arguments
197
+ def null?
198
+ splat? && keyword_splat?
199
+ end
200
+
201
+ def describe
202
+ ".with_signature(
203
+ required_arguments: #{self.required_arguments},
204
+ optional_arguments: #{self.optional_arguments},
205
+ required_keyword_arguments: #{self.required_keyword_arguments.to_a},
206
+ optional_keyword_arguments: #{self.optional_keyword_arguments.to_a},
207
+ splat: #{self.splat?},
208
+ keyword_splat: #{self.keyword_splat?})"
209
+ end
210
+
211
+ # Validates whether the given arguments match the expected signature
212
+ #
213
+ # @param [Array] args
214
+ # @raise ValidationFailed
215
+ def validate(args)
216
+ args = args.dup
217
+ kw_args = Hash.new
218
+
219
+ last_is_proc = false
220
+ begin
221
+ if args.last.kind_of?(Proc)
222
+ args.pop
223
+ last_is_proc = true
224
+ end
225
+ rescue NoMethodError
226
+ end
227
+
228
+ last_is_kw_hash = false
229
+ if expects_keyword_arguments?
230
+ last_is_kw_hash =
231
+ begin
232
+ args.last.kind_of?(Hash)
233
+ rescue NoMethodError
234
+ end
235
+
236
+ if last_is_kw_hash
237
+ kw_args = args.pop
238
+ elsif requires_keyword_arguments?
239
+ raise ValidationFailed, "#{@exp} expects keyword arguments but none were provided"
240
+ end
241
+ end
242
+
243
+ # There is currently no way to disambiguate "given a block" from "given a
244
+ # proc as last argument" ... give some leeway in this case
245
+ positional_count = args.size
246
+
247
+ if required_arguments > positional_count
248
+ if requires_keyword_arguments?
249
+ raise ValidationFailed, "#{@exp} expects at least #{required_arguments} positional arguments but got only #{positional_count}"
250
+ end
251
+
252
+ if (required_arguments - positional_count) == 1 && (last_is_kw_hash || last_is_proc)
253
+ if last_is_kw_hash
254
+ last_is_kw_hash = false
255
+ kw_args = Hash.new
256
+ else
257
+ last_is_proc = false
258
+ end
259
+ positional_count += 1
260
+ elsif (required_arguments - positional_count) == 2 && (last_is_kw_hash && last_is_proc)
261
+ last_is_kw_hash = false
262
+ kw_args = Hash.new
263
+ last_is_proc = false
264
+ positional_count += 2
265
+ else
266
+ raise ValidationFailed, "#{@exp} expects at least #{required_arguments} positional arguments but got only #{positional_count}"
267
+ end
268
+ end
269
+
270
+ if !splat? && (required_arguments + optional_arguments) < positional_count
271
+ if !last_is_proc || (required_arguments + optional_arguments) < positional_count - 1
272
+ raise ValidationFailed, "#{@exp} expects at most #{required_arguments + optional_arguments} positional arguments but got #{positional_count}"
273
+ end
274
+ end
275
+
276
+ missing_keyword_arguments = required_keyword_arguments.
277
+ find_all { |k| !kw_args.has_key?(k) }
278
+ if !missing_keyword_arguments.empty?
279
+ raise ValidationFailed, "#{@exp} missing required keyword arguments #{missing_keyword_arguments.map(&:to_s).sort.join(", ")}"
280
+ end
281
+ if !keyword_splat?
282
+ kw_args.each_key do |k|
283
+ if !optional_keyword_arguments.include?(k) && !required_keyword_arguments.include?(k)
284
+ raise ValidationFailed, "#{@exp} given unexpected keyword argument #{k}"
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ # Create a validator that represents the signature of an existing method
291
+ def self.from_instance_method(exp, instance_method)
292
+ required_arguments, optional_arguments, splat = 0, 0, false
293
+ required_keyword_arguments, optional_keyword_arguments, keyword_splat = Set.new, Set.new, false
294
+ instance_method.parameters.each do |type, name|
295
+ case type
296
+ when :req then required_arguments += 1
297
+ when :opt then optional_arguments += 1
298
+ when :rest then splat = true
299
+ when :keyreq then required_keyword_arguments << name
300
+ when :key then optional_keyword_arguments << name
301
+ when :keyrest then keyword_splat = true
302
+ when :block
303
+ else raise ArgumentError, "cannot interpret parameter type #{type}"
304
+ end
305
+ end
306
+ new(exp,
307
+ required_arguments: required_arguments,
308
+ optional_arguments: optional_arguments,
309
+ splat: splat,
310
+ required_keyword_arguments: required_keyword_arguments,
311
+ optional_keyword_arguments: optional_keyword_arguments,
312
+ keyword_splat: keyword_splat)
313
+ end
314
+ end
139
315
  end
316
+
@@ -1,3 +1,3 @@
1
1
  class FlexMock
2
- VERSION = "2.2.1"
2
+ VERSION = "2.3.8"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flexmock
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Weirich
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-07-12 00:00:00.000000000 Z
12
+ date: 2023-08-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
20
+ version: '5.0'
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
27
+ version: '5.0'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: rake
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -76,12 +76,14 @@ extensions: []
76
76
  extra_rdoc_files: []
77
77
  files:
78
78
  - ".autotest"
79
+ - ".github/workflows/test.yml"
79
80
  - ".gitignore"
80
81
  - ".togglerc"
81
82
  - ".travis.yml"
82
83
  - ".yardopts"
83
84
  - CHANGES
84
85
  - Gemfile
86
+ - LICENSE.txt
85
87
  - README.md
86
88
  - Rakefile
87
89
  - doc/GoogleExample.rdoc
@@ -177,15 +179,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
177
179
  requirements:
178
180
  - - ">="
179
181
  - !ruby/object:Gem::Version
180
- version: '2.0'
182
+ version: '2.2'
181
183
  required_rubygems_version: !ruby/object:Gem::Requirement
182
184
  requirements:
183
185
  - - ">="
184
186
  - !ruby/object:Gem::Version
185
187
  version: '0'
186
188
  requirements: []
187
- rubyforge_project:
188
- rubygems_version: 2.5.1
189
+ rubygems_version: 3.1.6
189
190
  signing_key:
190
191
  specification_version: 4
191
192
  summary: Simple and Flexible Mock Objects for Testing