grift 3.1.1 → 3.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c86b0996af61c492e67d267a83218c19c3840e6368f9e1111657bdd09e9a8904
4
- data.tar.gz: 1f23f5ccf98ab6f0f212d5284058334e06a252f90f0dee5ce4a8ef6f4d865c3e
3
+ metadata.gz: 4aff61073e962068bf8db43ccf6b17746c504a889725bbce4a7b545d81acece7
4
+ data.tar.gz: 96239a0ee6e279db817d13cd65566a74c193929e3bafcc8f2a2f2f5a2d594b60
5
5
  SHA512:
6
- metadata.gz: 21d1ea9659d78500130dcd7c0f058753f84d089f994b7bbec072d1b4fb268c7a81f11ba5c961cfe3f79030c9075cbc09de728a0cdcbfabef3f42be832621d87a
7
- data.tar.gz: 04024343f3b6779302e50fc77e9c01556955fa3bf0f666acfeff9831fc5c509ae6fb19a617f56324979b640e842cf11e1367ae13f200db97d08cd8fb3cfc7ff4
6
+ metadata.gz: 5bac2eec9ca2ae6a986aa778620eeb3af3b354cd9b63ce8bb94c5b58441be4b1b1a475d079309e03b9a81deec67e5d27dd3eeba3c7434cdea477f5c779763004
7
+ data.tar.gz: dad5770bb188d57df66c438ac77b989ad480cad1936d425394496ad4675b270b1aa6ab56096d633587545972644162db96b466b3751bb0f0968373f1280c6f52
data/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [3.2.0](https://github.com/clarkedb/grift/compare/v3.1.1...v3.2.0) (2026-10-14)
8
+
9
+
10
+ ### Added
11
+
12
+ * add an instance option to Grift.spy_on ([#308](https://github.com/clarkedb/grift/issues/308)) ([ffaea83](https://github.com/clarkedb/grift/commit/ffaea8395879241906f7fe5b3533647eafd40ea9))
13
+ * add Minitest assertions for mocks ([#312](https://github.com/clarkedb/grift/issues/312)) ([b4b6117](https://github.com/clarkedb/grift/commit/b4b61173489d6fa03e98892eab984d0d225068a0)), closes [#130](https://github.com/clarkedb/grift/issues/130)
14
+
15
+
16
+ ### Fixed
17
+
18
+ * record keyword arguments for mocked implementations ([#311](https://github.com/clarkedb/grift/issues/311)) ([858873e](https://github.com/clarkedb/grift/commit/858873ed332834f0bfc31ed20fade25209e283e9)), closes [#310](https://github.com/clarkedb/grift/issues/310)
19
+
7
20
  ## [3.1.1](https://github.com/clarkedb/grift/compare/v3.1.0...v3.1.1) (2026-10-14)
8
21
 
9
22
 
@@ -11,113 +24,124 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
11
24
 
12
25
  * package only runtime files in the gem ([#292](https://github.com/clarkedb/grift/issues/292)) ([0e6e3fd](https://github.com/clarkedb/grift/commit/0e6e3fdf2e99696468144ce490b015bdee3f13d4))
13
26
 
14
- ## [3.1.0](https://github.com/clarkedb/grift/releases/tag/v3.1.0) - 2026-01-03
27
+ ## [3.1.0](https://github.com/clarkedb/grift/compare/v3.0.0...v3.1.0) (2026-01-03)
28
+
15
29
 
16
30
  ### Added
17
31
 
18
- - Official support for Ruby 4.0 ([#274](https://github.com/clarkedb/grift/pull/274))
32
+ * Official support for Ruby 4.0 ([#274](https://github.com/clarkedb/grift/pull/274))
19
33
 
20
34
  ### Fixed
21
35
 
22
- - Migrate rubocop setup to use plugins ([#275](https://github.com/clarkedb/grift/pull/275))
23
- - Use preferred `method_defined?` for style ([#276](https://github.com/clarkedb/grift/pull/276))
36
+ * Migrate rubocop setup to use plugins ([#275](https://github.com/clarkedb/grift/pull/275))
37
+ * Use preferred `method_defined?` for style ([#276](https://github.com/clarkedb/grift/pull/276))
24
38
 
25
- ## [3.0.0](https://github.com/clarkedb/grift/releases/tag/v3.0.0) - 2024-12-26
39
+ ## [3.0.0](https://github.com/clarkedb/grift/compare/v2.2.0...v3.0.0) (2024-12-26)
26
40
 
27
- ### Breaking
28
41
 
29
- - All mocks are now scoped to the current thread for thread-safety ([#256](https://github.com/clarkedb/grift/pull/256))
30
- - For parallelized test suites using processes this is not a breaking change
31
- - This fixes intermittent test failures sometimes seen when codebases have threads calling mocked methods
42
+ ### ⚠ BREAKING CHANGES
43
+
44
+ * All mocks are now scoped to the current thread for thread-safety ([#256](https://github.com/clarkedb/grift/pull/256))
45
+ * For parallelized test suites using processes this is not a breaking change
46
+ * This fixes intermittent test failures sometimes seen when codebases have threads calling mocked methods
32
47
 
33
48
  ### Added
34
49
 
35
- - Official support for Ruby 3.4 ([#257](https://github.com/clarkedb/grift/pull/257))
36
- - Direct access to mock results/calls without calling `mock` ([#258](https://github.com/clarkedb/grift/pull/258))
50
+ * Official support for Ruby 3.4 ([#257](https://github.com/clarkedb/grift/pull/257))
51
+ * Direct access to mock results/calls without calling `mock` ([#258](https://github.com/clarkedb/grift/pull/258))
52
+
53
+ ## [2.2.0](https://github.com/clarkedb/grift/compare/v2.1.0...v2.2.0) (2024-01-13)
37
54
 
38
- ## [2.2.0](https://github.com/clarkedb/grift/releases/tag/v2.2.0) - 2024-01-13
39
55
 
40
56
  ### Added
41
57
 
42
- - Official support for Ruby 3.3 ([#203](https://github.com/clarkedb/grift/pull/203))
58
+ * Official support for Ruby 3.3 ([#203](https://github.com/clarkedb/grift/pull/203))
59
+
60
+ ## [2.1.0](https://github.com/clarkedb/grift/compare/v2.0.1...v2.1.0) (2022-12-27)
43
61
 
44
- ## [2.1.0](https://github.com/clarkedb/grift/releases/tag/v2.1.0) - 2022-12-27
45
62
 
46
63
  ### Added
47
64
 
48
- - Official support for Ruby 3.2 ([#126](https://github.com/clarkedb/grift/pull/126))
49
- - Support for finite/self-terminating mocking for more precision in testing where a method may get called multiple times
50
- - The Grift API now supports `mock_return_value_once`, `mock_return_value_n_times`, and `mock_return_values_in_order` ([#135](https://github.com/clarkedb/grift/pull/135))
51
- - The Grift API now supports `mock_implementation_once` and `mock_implementation_n_times` ([#136](https://github.com/clarkedb/grift/pull/136))
65
+ * Official support for Ruby 3.2 ([#126](https://github.com/clarkedb/grift/pull/126))
66
+ * Support for finite/self-terminating mocking for more precision in testing where a method may get called multiple times
67
+ * The Grift API now supports `mock_return_value_once`, `mock_return_value_n_times`, and `mock_return_values_in_order` ([#135](https://github.com/clarkedb/grift/pull/135))
68
+ * The Grift API now supports `mock_implementation_once` and `mock_implementation_n_times` ([#136](https://github.com/clarkedb/grift/pull/136))
69
+
70
+ ## [2.0.1](https://github.com/clarkedb/grift/compare/v2.0.0...v2.0.1) (2022-03-27)
52
71
 
53
- ## [2.0.1](https://github.com/clarkedb/grift/releases/tag/v2.0.1) - 2022-03-27
54
72
 
55
73
  ### Fixed
56
74
 
57
- - When spying on a method that takes a block, the block now gets forwarded to the original method ([#78](https://github.com/clarkedb/grift/pull/78))
58
- - When mocking the implementation, if a block is not provided a `Grift::Error` is raised instead of a `LocalJumpError` ([#77](https://github.com/clarkedb/grift/pull/77))
75
+ * When spying on a method that takes a block, the block now gets forwarded to the original method ([#78](https://github.com/clarkedb/grift/pull/78))
76
+ * When mocking the implementation, if a block is not provided a `Grift::Error` is raised instead of a `LocalJumpError` ([#77](https://github.com/clarkedb/grift/pull/77))
77
+
78
+ ## [2.0.0](https://github.com/clarkedb/grift/compare/v1.1.0...v2.0.0) (2022-03-14)
59
79
 
60
- ## [2.0.0](https://github.com/clarkedb/grift/releases/tag/v2.0.0) - 2022-03-14
61
80
 
62
81
  This version adds true keyword argument support for Ruby 3. See below for how to handle breaking changes.
63
82
 
64
- ### Changed
83
+ ### ⚠ BREAKING CHANGES
65
84
 
66
- - Dropped support for Ruby 2.5 ([#69](https://github.com/clarkedb/grift/pull/69))
67
- - Dropped support for Ruby 2.6 ([#72](https://github.com/clarkedb/grift/pull/72))
68
- - To support keyword arguments, records of call arguments are no longer stored in simple arrays but in a custom Enumerable ([#72](https://github.com/clarkedb/grift/pull/72))
69
- - This changes the way that your tests will interact with mock calls
70
- - When before `calls` returned an array, it returns a `Grift::MockMethod::MockExecutions::MockArguments` object
71
- - Migrating to maintain previous behavior just requires appending `.args` to `calls[i]`
85
+ * Dropped support for Ruby 2.5 ([#69](https://github.com/clarkedb/grift/pull/69))
86
+ * Dropped support for Ruby 2.6 ([#72](https://github.com/clarkedb/grift/pull/72))
87
+ * To support keyword arguments, records of call arguments are no longer stored in simple arrays but in a custom Enumerable ([#72](https://github.com/clarkedb/grift/pull/72))
88
+ * This changes the way that your tests will interact with mock calls
89
+ * When before `calls` returned an array, it returns a `Grift::MockMethod::MockExecutions::MockArguments` object
90
+ * Migrating to maintain previous behavior just requires appending `.args` to `calls[i]`
72
91
 
73
92
  ### Added
74
93
 
75
- - Support for mocking private instance and class methods ([#68](https://github.com/clarkedb/grift/pull/68))
76
- - Support for mocking methods that take positional and keyword arguments ([#72](https://github.com/clarkedb/grift/pull/72))
94
+ * Support for mocking private instance and class methods ([#68](https://github.com/clarkedb/grift/pull/68))
95
+ * Support for mocking methods that take positional and keyword arguments ([#72](https://github.com/clarkedb/grift/pull/72))
77
96
 
78
97
  ### Fixed
79
98
 
80
- - When mocking protected methods, the method now remains protected while mocked and after unmocking ([#68](https://github.com/clarkedb/grift/pull/68))
81
- - When mocking inherited methods, the method goes back to the ancestor's definition after unmocking ([#69](https://github.com/clarkedb/grift/pull/69))
82
- - When mocking methods with keyword arugments in Ruby 3.x, no error is thrown ([#72](https://github.com/clarkedb/grift/pull/72))
99
+ * When mocking protected methods, the method now remains protected while mocked and after unmocking ([#68](https://github.com/clarkedb/grift/pull/68))
100
+ * When mocking inherited methods, the method goes back to the ancestor's definition after unmocking ([#69](https://github.com/clarkedb/grift/pull/69))
101
+ * When mocking methods with keyword arugments in Ruby 3.x, no error is thrown ([#72](https://github.com/clarkedb/grift/pull/72))
102
+
103
+ ## [1.1.0](https://github.com/clarkedb/grift/compare/v1.0.2...v1.1.0) (2022-02-03)
83
104
 
84
- ## [1.1.0](https://github.com/clarkedb/grift/releases/tag/v1.1.0) - 2022-02-03
85
105
 
86
106
  This version adds support for Ruby 3.1 and updates various dependencies.
87
107
 
88
108
  ### Added
89
109
 
90
- - Support Ruby 3.1 ([#52](https://github.com/clarkedb/grift/pull/52))
110
+ * Support Ruby 3.1 ([#52](https://github.com/clarkedb/grift/pull/52))
111
+
112
+ ## [1.0.2](https://github.com/clarkedb/grift/compare/v1.0.1...v1.0.2) (2021-11-11)
91
113
 
92
- ## [1.0.2](https://github.com/clarkedb/grift/releases/tag/v1.0.2) - 2021-11-11
93
114
 
94
115
  This version fixes a bug that prevented the mocking of methods defined by a class's super class.
95
116
 
96
117
  ### Fixed
97
118
 
98
- - Allow mocks of inherited methods ([#34](https://github.com/clarkedb/grift/pull/34))
119
+ * Allow mocks of inherited methods ([#34](https://github.com/clarkedb/grift/pull/34))
120
+
121
+ ## [1.0.1](https://github.com/clarkedb/grift/compare/v1.0.0...v1.0.1) (2021-11-10)
99
122
 
100
- ## [1.0.1](https://github.com/clarkedb/grift/releases/tag/v1.0.1) - 2021-11-10
101
123
 
102
124
  This version fixes a bug that prevented most mocking features in Grift from functioning as expected.
103
125
 
104
126
  ### Fixed
105
127
 
106
- - Uses relative path for yaml config files ([#28](https://github.com/clarkedb/grift/pull/28))
128
+ * Uses relative path for yaml config files ([#28](https://github.com/clarkedb/grift/pull/28))
129
+
130
+ ## [1.0.0](https://github.com/clarkedb/grift/compare/v0.1.0...v1.0.0) (2021-11-06)
107
131
 
108
- ## [1.0.0](https://github.com/clarkedb/grift/releases/tag/v1.0.0) - 2021-11-06
109
132
 
110
133
  The first major version of Grift! 100% documentation and 100% code coverage.
111
134
 
112
135
  ### Added
113
136
 
114
- - Spying on method ([#9](https://github.com/clarkedb/grift/pull/9))
115
- - Mocking method return values ([#9](https://github.com/clarkedb/grift/pull/9))
116
- - Mocking method implementation ([#13](https://github.com/clarkedb/grift/pull/13))
117
- - Restricted methods that cannot be mocked ([#20](https://github.com/clarkedb/grift/pull/20))
118
- - MiniTest Plugin to use hooks and clean up after tests ([#17](https://github.com/clarkedb/grift/pull/17))
119
- - Documentation! ([#23](https://github.com/clarkedb/grift/pull/23))
137
+ * Spying on method ([#9](https://github.com/clarkedb/grift/pull/9))
138
+ * Mocking method return values ([#9](https://github.com/clarkedb/grift/pull/9))
139
+ * Mocking method implementation ([#13](https://github.com/clarkedb/grift/pull/13))
140
+ * Restricted methods that cannot be mocked ([#20](https://github.com/clarkedb/grift/pull/20))
141
+ * MiniTest Plugin to use hooks and clean up after tests ([#17](https://github.com/clarkedb/grift/pull/17))
142
+ * Documentation! ([#23](https://github.com/clarkedb/grift/pull/23))
143
+
144
+ ## 0.1.0 (2021-10-12)
120
145
 
121
- ## [0.1.0](https://github.com/clarkedb/grift/releases/tag/v0.1.0) - 2021-10-12
122
146
 
123
147
  The initial version of Grift. This was never intended for use and should be considered **deprecated**.
data/README.md CHANGED
@@ -28,7 +28,7 @@ gem install grift
28
28
 
29
29
  ### MiniTest Plugin
30
30
 
31
- We recommend using the plugin so that mocks are cleaned up after each test automatically. To enable the plugin, add the following lines of code to your `test_helper` file.
31
+ We recommend using the plugin so that mocks are cleaned up after each test automatically. The plugin also adds [assertions](#assertions) for mocks. To enable the plugin, add the following lines of code to your `test_helper` file.
32
32
 
33
33
  ```ruby
34
34
  class Minitest::Test
@@ -87,6 +87,14 @@ my_spy.mock_implementation do |arg1, arg2, **kwargs|
87
87
  end
88
88
  ```
89
89
 
90
+ ### Class and instance methods with the same name
91
+
92
+ When a class responds to a method itself, Grift mocks the class method. That includes methods every class has, like `name`, `to_s`, and `==`. To mock the instance method instead, pass `instance: true` to `spy_on`:
93
+
94
+ ```ruby
95
+ my_mock = Grift.spy_on(MyClass, :name, instance: true).mock_return_value('mocked')
96
+ ```
97
+
90
98
  ### Chaining
91
99
 
92
100
  You can chain `mock_return_value` and `mock_implementation` after initializing the mock.
@@ -120,6 +128,24 @@ my_mock.results
120
128
  #=> ['result1', 'result2']
121
129
  ```
122
130
 
131
+ ### Assertions
132
+
133
+ With the [MiniTest Plugin](#minitest-plugin) included, you can assert on mocks directly in your tests.
134
+
135
+ ```ruby
136
+ assert_mocked MyClass, :my_method
137
+ refute_mocked MyClass, :other_method
138
+
139
+ assert_mock_called my_mock
140
+ refute_mock_called my_mock
141
+
142
+ # passes if any call used exactly these arguments
143
+ assert_mock_called_with my_mock, 'first_arg', key: 'value'
144
+ refute_mock_called_with my_mock, 'other_arg'
145
+ ```
146
+
147
+ When an assertion fails, the message lists the calls to the mocked method.
148
+
123
149
  ## Requirements
124
150
 
125
151
  Grift supports all Ruby versions >= 2.7 (including 4.x versions).
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Grift
4
4
  ##
5
- # A plugin to clean up mocks after tests in Minitest.
5
+ # A plugin to clean up mocks after tests in Minitest and to assert on mocks.
6
6
  # It is recommended that you include this plugin to avoid
7
7
  # needing to cleanup the mocks after each test.
8
8
  #
@@ -23,5 +23,157 @@ module Grift
23
23
  super
24
24
  Grift.restore_all_mocks(watch: false)
25
25
  end
26
+
27
+ ##
28
+ # Asserts that the given method is currently mocked.
29
+ #
30
+ # @see Grift.mock_method?
31
+ #
32
+ # @example
33
+ # Grift.spy_on(MyClass, :my_method)
34
+ # assert_mocked(MyClass, :my_method)
35
+ #
36
+ # @param klass [Class] the class of the method to be checked
37
+ # @param method [Symbol] the symbol representing the method to be checked
38
+ # @param msg [String, Proc] a message to show before the failure message
39
+ #
40
+ # @return [true]
41
+ #
42
+ def assert_mocked(klass, method, msg = nil)
43
+ key = Grift::MockMethod.hash_key(klass, method)
44
+ assert Grift.mock_method?(klass, method), message(msg) { "Expected #{key} to be mocked" }
45
+ end
46
+
47
+ ##
48
+ # Asserts that the given method is not currently mocked.
49
+ #
50
+ # @see Grift.mock_method?
51
+ #
52
+ # @example
53
+ # refute_mocked(MyClass, :my_method)
54
+ #
55
+ # @param klass [Class] the class of the method to be checked
56
+ # @param method [Symbol] the symbol representing the method to be checked
57
+ # @param msg [String, Proc] a message to show before the failure message
58
+ #
59
+ # @return [true]
60
+ #
61
+ def refute_mocked(klass, method, msg = nil)
62
+ key = Grift::MockMethod.hash_key(klass, method)
63
+ refute Grift.mock_method?(klass, method), message(msg) { "Expected #{key} to not be mocked" }
64
+ end
65
+
66
+ ##
67
+ # Asserts that the mocked method has been called at least once.
68
+ #
69
+ # @example
70
+ # my_mock = Grift.spy_on(MyClass, :my_method)
71
+ # MyClass.my_method
72
+ # assert_mock_called(my_mock)
73
+ #
74
+ # @param mock [Grift::MockMethod] the mock to be checked
75
+ # @param msg [String, Proc] a message to show before the failure message
76
+ #
77
+ # @return [true]
78
+ #
79
+ def assert_mock_called(mock, msg = nil)
80
+ refute mock.empty?, message(msg) { "Expected #{mock} to be called" }
81
+ end
82
+
83
+ ##
84
+ # Asserts that the mocked method has not been called.
85
+ #
86
+ # @example
87
+ # my_mock = Grift.spy_on(MyClass, :my_method)
88
+ # refute_mock_called(my_mock)
89
+ #
90
+ # @param mock [Grift::MockMethod] the mock to be checked
91
+ # @param msg [String, Proc] a message to show before the failure message
92
+ #
93
+ # @return [true]
94
+ #
95
+ def refute_mock_called(mock, msg = nil)
96
+ failure = message(msg, '') do
97
+ "Expected #{mock} to not be called, but it was called with:\n#{mock_calls_description(mock)}"
98
+ end
99
+ assert mock.empty?, failure
100
+ end
101
+
102
+ ##
103
+ # Asserts that the mocked method has been called at least once with exactly the given arguments.
104
+ # Every argument is matched against the calls, so there is no custom message parameter.
105
+ #
106
+ # @example
107
+ # my_mock = Grift.spy_on(MyClass, :my_method)
108
+ # MyClass.my_method('first', second: 2)
109
+ # assert_mock_called_with(my_mock, 'first', second: 2)
110
+ #
111
+ # @param mock [Grift::MockMethod] the mock to be checked
112
+ # @param args [Array] the expected positional arguments
113
+ # @param kwargs [Hash] the expected keyword arguments
114
+ #
115
+ # @return [true]
116
+ #
117
+ def assert_mock_called_with(mock, *args, **kwargs)
118
+ failure = proc do
119
+ expected = "Expected #{mock} to be called with #{mock_call_description(args, kwargs)}"
120
+ if mock.empty?
121
+ "#{expected}, but it was not called."
122
+ else
123
+ "#{expected}, but it was called with:\n#{mock_calls_description(mock)}"
124
+ end
125
+ end
126
+ assert mock_called_with?(mock, args, kwargs), failure
127
+ end
128
+
129
+ ##
130
+ # Asserts that the mocked method has not been called with exactly the given arguments.
131
+ # Every argument is matched against the calls, so there is no custom message parameter.
132
+ #
133
+ # @example
134
+ # my_mock = Grift.spy_on(MyClass, :my_method)
135
+ # MyClass.my_method('first', second: 2)
136
+ # refute_mock_called_with(my_mock, 'first', second: 3)
137
+ #
138
+ # @param mock [Grift::MockMethod] the mock to be checked
139
+ # @param args [Array] the positional arguments that should not have been used
140
+ # @param kwargs [Hash] the keyword arguments that should not have been used
141
+ #
142
+ # @return [true]
143
+ #
144
+ def refute_mock_called_with(mock, *args, **kwargs)
145
+ failure = proc { "Expected #{mock} to not be called with #{mock_call_description(args, kwargs)}." }
146
+ refute mock_called_with?(mock, args, kwargs), failure
147
+ end
148
+
149
+ private
150
+
151
+ ##
152
+ # Checks if any call to the mock used exactly the given arguments.
153
+ #
154
+ # @return [Boolean]
155
+ #
156
+ def mock_called_with?(mock, args, kwargs)
157
+ mock.calls.any? { |call| args == call.args && kwargs == call.kwargs }
158
+ end
159
+
160
+ ##
161
+ # Formats the arguments of a call like they would be written in Ruby.
162
+ #
163
+ # @return [String]
164
+ #
165
+ def mock_call_description(args, kwargs)
166
+ values = args.map { |arg| mu_pp(arg) } + kwargs.map { |key, value| "#{key}: #{mu_pp(value)}" }
167
+ "(#{values.join(', ')})"
168
+ end
169
+
170
+ ##
171
+ # Formats every call to the mock, one per line.
172
+ #
173
+ # @return [String]
174
+ #
175
+ def mock_calls_description(mock)
176
+ mock.calls.map { |call| " #{mock_call_description(call.args, call.kwargs)}" }.join("\n")
177
+ end
26
178
  end
27
179
  end
@@ -40,10 +40,11 @@ module Grift
40
40
  # @param klass [Class] the class to be mocked
41
41
  # @param method_name [Symbol] the method to be mocked
42
42
  # @param watch [Boolean] whether to start watching the method
43
+ # @param instance [Boolean] whether to mock the instance method even if the class responds to the method itself
43
44
  #
44
45
  # @return [Grift::MockMethod]
45
46
  #
46
- def initialize(klass, method_name, watch: true)
47
+ def initialize(klass, method_name, watch: true, instance: false)
47
48
  if Grift.restricted_method?(klass, method_name)
48
49
  raise(Grift::Error, "Cannot mock restricted method #{method_name} for class #{klass}")
49
50
  end
@@ -55,15 +56,11 @@ module Grift
55
56
  @cache_method_name = :"#{CACHE_METHOD_PREFIX}_#{method_name}"
56
57
 
57
58
  # class methods are really instance methods of the singleton class
58
- @class_method = klass.singleton_class.method_defined?(method_name, true) ||
59
- klass.singleton_class.private_method_defined?(method_name, true)
59
+ @class_method = !instance && defined_on?(klass.singleton_class, method_name)
60
60
 
61
61
  @method_access, @inherited = method_access_definition
62
62
  raise(Grift::Error, "Cannot mock unknown method #{method_name} for class #{klass}") unless @method_access
63
-
64
- if class_instance.method_defined?(@cache_method_name)
65
- raise(Grift::Error, "Cannot mock already mocked method #{method_name} for class #{klass}")
66
- end
63
+ raise(Grift::Error, "Cannot mock already mocked method #{method_name} for class #{klass}") if already_mocked?
67
64
 
68
65
  watch_method if watch
69
66
  end
@@ -160,7 +157,7 @@ module Grift
160
157
  return_value = yield(*args, **kwargs)
161
158
 
162
159
  # record the args passed in the call to the method and the result
163
- mock_executions.store(args: args, result: return_value)
160
+ mock_executions.store(args: args, kwargs: kwargs, result: return_value)
164
161
  return_value
165
162
  end
166
163
  class_instance.send(@method_access, @method_name)
@@ -203,7 +200,7 @@ module Grift
203
200
  return_value = yield(*args, **kwargs)
204
201
 
205
202
  # record the args passed in the call to the method and the result
206
- mock_executions.store(args: args, result: return_value)
203
+ mock_executions.store(args: args, kwargs: kwargs, result: return_value)
207
204
 
208
205
  clean_mock.call
209
206
 
@@ -266,7 +263,7 @@ module Grift
266
263
  return_value = yield(*args, **kwargs)
267
264
 
268
265
  # record the args passed in the call to the method and the result
269
- mock_executions.store(args: args, result: return_value)
266
+ mock_executions.store(args: args, kwargs: kwargs, result: return_value)
270
267
 
271
268
  clean_mock.call if mock_executions.count == n
272
269
 
@@ -618,8 +615,34 @@ module Grift
618
615
  # @return [Boolean]
619
616
  #
620
617
  def method_defined?
621
- class_instance.method_defined?(@method_name, false) ||
622
- class_instance.private_method_defined?(@method_name, false)
618
+ defined_on?(class_instance, @method_name, inherit: false)
619
+ end
620
+
621
+ ##
622
+ # Checks if the method is already mocked. A class method and an instance method
623
+ # with the same name share a key in the mock store, so mocking either one counts.
624
+ #
625
+ # @return [Boolean]
626
+ #
627
+ def already_mocked?
628
+ owners = [@klass.singleton_class]
629
+ # an object can be mocked directly, and it has no instance methods of its own
630
+ owners << @klass if @klass.is_a?(Module)
631
+
632
+ owners.any? { |owner| defined_on?(owner, @cache_method_name) }
633
+ end
634
+
635
+ ##
636
+ # Checks if the owner defines the method with any access (public, protected, private).
637
+ #
638
+ # @param owner [Module] the class or singleton class to check
639
+ # @param method_name [Symbol] the method to look for
640
+ # @param inherit [Boolean] whether to include methods defined by ancestors
641
+ #
642
+ # @return [Boolean]
643
+ #
644
+ def defined_on?(owner, method_name, inherit: true)
645
+ owner.method_defined?(method_name, inherit) || owner.private_method_defined?(method_name, inherit)
623
646
  end
624
647
 
625
648
  ##
data/lib/grift/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Grift
4
4
  # gem version
5
- VERSION = '3.1.1'
5
+ VERSION = '3.2.0'
6
6
  public_constant :VERSION
7
7
  end
data/lib/grift.rb CHANGED
@@ -25,7 +25,9 @@ module Grift
25
25
  ##
26
26
  # Mocks the given method to return the provided value.
27
27
  # This is syntactical sugar equivalent to calling
28
- # +spy_on+ and then +mock_return_value+.
28
+ # +spy_on+ and then +mock_return_value+. To mock an instance method
29
+ # when the class responds to a method of the same name, use +spy_on+
30
+ # with +instance: true+.
29
31
  #
30
32
  # @example
31
33
  # my_mock = Grift.mock(MyClass, :some_method, true)
@@ -46,18 +48,27 @@ module Grift
46
48
  # Creates a mock for the given method without mocking
47
49
  # the implementation or return values.
48
50
  #
51
+ # If the class responds to the method itself, the class method is
52
+ # watched. This includes methods every class has, like +name+,
53
+ # +to_s+, and +==+. Pass +instance: true+ to watch the instance
54
+ # method of the same name instead.
55
+ #
49
56
  # @example
50
57
  # my_mock = Grift.spy_on(MyClass, :some_method)
51
58
  #
59
+ # @example
60
+ # my_mock = Grift.spy_on(MyClass, :name, instance: true)
61
+ #
52
62
  # @param klass [Class] the class of the method to be watched
53
63
  # @param method [Symbol] the symbol representing the method to be watched
64
+ # @param instance [Boolean] if true, watch the instance method even if the class responds to the method itself
54
65
  #
55
66
  # @raise [Grift::Error] exception if method is already mocked or does not exist
56
67
  #
57
68
  # @return [Grift::MockMethod]
58
69
  #
59
- def spy_on(klass, method)
60
- MockMethod.new(klass, method)
70
+ def spy_on(klass, method, instance: false)
71
+ MockMethod.new(klass, method, instance: instance)
61
72
  end
62
73
 
63
74
  ##
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grift
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clark Brown