grift 3.1.0 → 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: e43df3a93829be7aaf802bf4799dd2c12e741bf254e15fd08a7db816278003d4
4
- data.tar.gz: 5811b46cb932445c62824e87367258563ec7bed4accc78eb04dc8833472662ce
3
+ metadata.gz: 4aff61073e962068bf8db43ccf6b17746c504a889725bbce4a7b545d81acece7
4
+ data.tar.gz: 96239a0ee6e279db817d13cd65566a74c193929e3bafcc8f2a2f2f5a2d594b60
5
5
  SHA512:
6
- metadata.gz: e2d9170206163bc65f9542c92d5c6d981cf19d89eba2afd86e578ac587fbb800a3a2c63704438dcf4eed3e46a7bf03e9277b32c09dec364860c3d9e4cbcd7f71
7
- data.tar.gz: 647225ba9c65ecb2707fe1c6eca06fa02c6a8b8e12a4052e6c686e8b6edb58df8c3043cab1a7cb6dda428aa75e36cc9d6c79329a54291e47f58e37c2cd69e132
6
+ metadata.gz: 5bac2eec9ca2ae6a986aa778620eeb3af3b354cd9b63ce8bb94c5b58441be4b1b1a475d079309e03b9a81deec67e5d27dd3eeba3c7434cdea477f5c779763004
7
+ data.tar.gz: dad5770bb188d57df66c438ac77b989ad480cad1936d425394496ad4675b270b1aa6ab56096d633587545972644162db96b466b3751bb0f0968373f1280c6f52
data/CHANGELOG.md CHANGED
@@ -4,117 +4,144 @@ 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
- ## Unreleased
7
+ ## [3.2.0](https://github.com/clarkedb/grift/compare/v3.1.1...v3.2.0) (2026-10-14)
8
8
 
9
- None
10
9
 
11
- ## [3.1.0](https://github.com/clarkedb/grift/releases/tag/v3.1.0) - 2026-01-03
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
+
20
+ ## [3.1.1](https://github.com/clarkedb/grift/compare/v3.1.0...v3.1.1) (2026-10-14)
21
+
22
+
23
+ ### Fixed
24
+
25
+ * package only runtime files in the gem ([#292](https://github.com/clarkedb/grift/issues/292)) ([0e6e3fd](https://github.com/clarkedb/grift/commit/0e6e3fdf2e99696468144ce490b015bdee3f13d4))
26
+
27
+ ## [3.1.0](https://github.com/clarkedb/grift/compare/v3.0.0...v3.1.0) (2026-01-03)
28
+
12
29
 
13
30
  ### Added
14
31
 
15
- - 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))
16
33
 
17
34
  ### Fixed
18
35
 
19
- - Migrate rubocop setup to use plugins ([#275](https://github.com/clarkedb/grift/pull/275))
20
- - 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))
38
+
39
+ ## [3.0.0](https://github.com/clarkedb/grift/compare/v2.2.0...v3.0.0) (2024-12-26)
21
40
 
22
- ## [3.0.0](https://github.com/clarkedb/grift/releases/tag/v3.0.0) - 2024-12-26
23
41
 
24
- ### Breaking
42
+ ### ⚠ BREAKING CHANGES
25
43
 
26
- - All mocks are now scoped to the current thread for thread-safety ([#256](https://github.com/clarkedb/grift/pull/256))
27
- - For parallelized test suites using processes this is not a breaking change
28
- - This fixes intermittent test failures sometimes seen when codebases have threads calling mocked methods
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
29
47
 
30
48
  ### Added
31
49
 
32
- - Official support for Ruby 3.4 ([#257](https://github.com/clarkedb/grift/pull/257))
33
- - 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)
34
54
 
35
- ## [2.2.0](https://github.com/clarkedb/grift/releases/tag/v2.2.0) - 2024-01-13
36
55
 
37
56
  ### Added
38
57
 
39
- - 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)
40
61
 
41
- ## [2.1.0](https://github.com/clarkedb/grift/releases/tag/v2.1.0) - 2022-12-27
42
62
 
43
63
  ### Added
44
64
 
45
- - Official support for Ruby 3.2 ([#126](https://github.com/clarkedb/grift/pull/126))
46
- - Support for finite/self-terminating mocking for more precision in testing where a method may get called multiple times
47
- - 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))
48
- - 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)
49
71
 
50
- ## [2.0.1](https://github.com/clarkedb/grift/releases/tag/v2.0.1) - 2022-03-27
51
72
 
52
73
  ### Fixed
53
74
 
54
- - 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))
55
- - 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)
56
79
 
57
- ## [2.0.0](https://github.com/clarkedb/grift/releases/tag/v2.0.0) - 2022-03-14
58
80
 
59
81
  This version adds true keyword argument support for Ruby 3. See below for how to handle breaking changes.
60
82
 
61
- ### Changed
83
+ ### ⚠ BREAKING CHANGES
62
84
 
63
- - Dropped support for Ruby 2.5 ([#69](https://github.com/clarkedb/grift/pull/69))
64
- - Dropped support for Ruby 2.6 ([#72](https://github.com/clarkedb/grift/pull/72))
65
- - 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))
66
- - This changes the way that your tests will interact with mock calls
67
- - When before `calls` returned an array, it returns a `Grift::MockMethod::MockExecutions::MockArguments` object
68
- - 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]`
69
91
 
70
92
  ### Added
71
93
 
72
- - Support for mocking private instance and class methods ([#68](https://github.com/clarkedb/grift/pull/68))
73
- - 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))
74
96
 
75
97
  ### Fixed
76
98
 
77
- - When mocking protected methods, the method now remains protected while mocked and after unmocking ([#68](https://github.com/clarkedb/grift/pull/68))
78
- - When mocking inherited methods, the method goes back to the ancestor's definition after unmocking ([#69](https://github.com/clarkedb/grift/pull/69))
79
- - 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)
80
104
 
81
- ## [1.1.0](https://github.com/clarkedb/grift/releases/tag/v1.1.0) - 2022-02-03
82
105
 
83
106
  This version adds support for Ruby 3.1 and updates various dependencies.
84
107
 
85
108
  ### Added
86
109
 
87
- - 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)
88
113
 
89
- ## [1.0.2](https://github.com/clarkedb/grift/releases/tag/v1.0.2) - 2021-11-11
90
114
 
91
115
  This version fixes a bug that prevented the mocking of methods defined by a class's super class.
92
116
 
93
117
  ### Fixed
94
118
 
95
- - 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)
96
122
 
97
- ## [1.0.1](https://github.com/clarkedb/grift/releases/tag/v1.0.1) - 2021-11-10
98
123
 
99
124
  This version fixes a bug that prevented most mocking features in Grift from functioning as expected.
100
125
 
101
126
  ### Fixed
102
127
 
103
- - 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)
104
131
 
105
- ## [1.0.0](https://github.com/clarkedb/grift/releases/tag/v1.0.0) - 2021-11-06
106
132
 
107
133
  The first major version of Grift! 100% documentation and 100% code coverage.
108
134
 
109
135
  ### Added
110
136
 
111
- - Spying on method ([#9](https://github.com/clarkedb/grift/pull/9))
112
- - Mocking method return values ([#9](https://github.com/clarkedb/grift/pull/9))
113
- - Mocking method implementation ([#13](https://github.com/clarkedb/grift/pull/13))
114
- - Restricted methods that cannot be mocked ([#20](https://github.com/clarkedb/grift/pull/20))
115
- - MiniTest Plugin to use hooks and clean up after tests ([#17](https://github.com/clarkedb/grift/pull/17))
116
- - 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)
117
145
 
118
- ## [0.1.0](https://github.com/clarkedb/grift/releases/tag/v0.1.0) - 2021-10-12
119
146
 
120
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).
@@ -132,11 +158,20 @@ For now Grift is not guaranteed to be compatible with [`Ruby::Box`](https://docs
132
158
 
133
159
  After forking the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
134
160
 
135
- When developing, to install Grift whith your changes onto your local machine, run `bundle exec rake install` . For those with write access: to release a new version, update the version number in `version.rb` , and then run `bundle exec rake release` , which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
161
+ When developing, to install Grift whith your changes onto your local machine, run `bundle exec rake install` .
162
+
163
+ ### Releasing
164
+
165
+ Releases are managed by [release-please](https://github.com/googleapis/release-please). When releasable commits (`feat`, `fix`, `perf`, or breaking changes; see [CONTRIBUTING](.github/CONTRIBUTING.md#pull-request-titles)) land on `main`, it opens (or updates) a release pull request that bumps `lib/grift/version.rb` and `CHANGELOG.md`. For those with write access:
166
+
167
+ 1. Merge the release pull request, which tags the version and creates the GitHub release
168
+ 1. Once CI passes on `main`, approve the `release` deployment in GitHub Actions, which pushes the `.gem` file to [rubygems.org](https://rubygems.org) via trusted publishing and then deploys the [docs](#docs)
169
+
170
+ If publishing fails, use "Re-run failed jobs" on the workflow run. "Re-run all jobs" re-runs release-please, which finds the release already exists and skips publishing.
136
171
 
137
172
  ### Docs
138
173
 
139
- The docs are generated using YARD. To build the docs, first `gem install yard` . Then run `yardoc` to build the new docs. This is always done before a release to update the docs that get published and made available with the gem.
174
+ The docs are generated using YARD and deployed to [GitHub Pages](https://clarkedb.github.io/grift/) after each release. To redeploy them, run the Docs workflow manually with the latest release tag. To build them locally, run `BUNDLE_WITH=docs bundle install` and then `bundle exec yard` (output goes to the gitignored `docs/` folder).
140
175
 
141
176
  ## Contributing
142
177
 
@@ -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_instance_methods(false).include?(@method_name)
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.0'
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.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clark Brown
@@ -16,23 +16,9 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
- - ".github/CODEOWNERS"
20
- - ".github/CONTRIBUTING.md"
21
- - ".github/FUNDING.yml"
22
- - ".github/dependabot.yml"
23
- - ".github/workflows/ci.yml"
24
- - ".gitignore"
25
- - ".overcommit.yml"
26
- - ".rubocop.yml"
27
- - ".yardopts"
28
19
  - CHANGELOG.md
29
- - Gemfile
30
20
  - LICENSE
31
21
  - README.md
32
- - Rakefile
33
- - bin/console
34
- - bin/setup
35
- - grift.gemspec
36
22
  - lib/grift.rb
37
23
  - lib/grift/config.rb
38
24
  - lib/grift/config/restricted.yml
@@ -67,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
53
  - !ruby/object:Gem::Version
68
54
  version: '0'
69
55
  requirements: []
70
- rubygems_version: 4.0.3
56
+ rubygems_version: 4.0.20
71
57
  specification_version: 4
72
58
  summary: Mocking and spying in MiniTest
73
59
  test_files: []
data/.github/CODEOWNERS DELETED
@@ -1 +0,0 @@
1
- .github/ @clarkedb
@@ -1,37 +0,0 @@
1
- # How to become a contributor and submit your own code
2
-
3
- 1. Submit an issue on GitHub describing your proposed change to this repo
4
- 1. A repo owner will respond to your issue promptly
5
- 1. Fork the repo, develop and test your code changes
6
- 1. Ensure that your code adheres to the existing style in the code to which you are contributing
7
- 1. Ensure that your code has an appropriate set of tests which all pass
8
- 1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling
9
- 1. Submit a pull request.
10
-
11
- ## Policy on inclusive language
12
-
13
- To make Grift a pleasant and effective experience for everyone, we use try to use inclusive language.
14
-
15
- These resources can help:
16
-
17
- - Google's tutorial [Writing inclusive documentation](https://developers.google.com/style/inclusive-documentation) teaches by example, how to reword non-inclusive things.
18
- - Linux kernel mailing list's [Coding Style: Inclusive Terminology](https://lkml.org/lkml/2020/7/4/229) said "Add no new instances of non-inclusive words, here is a list of words not include new ones of."
19
- - Linguistic Society of America published [Guidelines for Inclusive Language](https://www.linguisticsociety.org/resource/guidelines-inclusive-language) which concluded: "We encourage all linguists to consider the possible reactions of their potential audience to their writing and, in so doing, to choose expository practices and content that is positive, inclusive, and respectful."
20
-
21
- This project attempts to improve in these areas. Join us in doing that important work.
22
-
23
- ## Required checks
24
-
25
- Before pushing your code and opening a PR, we recommend you run the following checks to avoid
26
- our GitHub Actions Workflow to block your contribution.
27
-
28
- ```bash
29
- # install dependencies
30
- $ bundle install
31
-
32
- # Run unit tests and check code coverage
33
- $ bundle exec rake test
34
-
35
- # Check code style
36
- $ bundle exec rubocop
37
- ```
data/.github/FUNDING.yml DELETED
@@ -1,3 +0,0 @@
1
- # github sponsors
2
- github:
3
- - clarkedb
@@ -1,13 +0,0 @@
1
- version: 2
2
-
3
- updates:
4
- - directory: /
5
- open-pull-requests-limit: 10
6
- package-ecosystem: bundler
7
- target-branch: main
8
- schedule:
9
- interval: monthly
10
- labels:
11
- - dependencies
12
- assignees:
13
- - clarkedb
@@ -1,63 +0,0 @@
1
- name: CI
2
-
3
- on: push
4
-
5
- env:
6
- CI: true
7
- GIT_COMMIT_SHA: ${{ github.sha }}
8
- GIT_BRANCH: ${{ github.ref }}
9
-
10
- jobs:
11
- linting:
12
- runs-on: ubuntu-latest
13
- env:
14
- BUNDLE_WITH: lint
15
- BUNDLE_WITHOUT: development:test
16
-
17
- steps:
18
- - uses: actions/checkout@v6
19
-
20
- - name: Set up Ruby 3.3
21
- uses: ruby/setup-ruby@v1
22
- with:
23
- ruby-version: 3.3
24
- bundler-cache: true
25
-
26
- - name: Rubocop
27
- run: bundle exec rubocop --format progress
28
-
29
- build:
30
- needs: [linting]
31
- runs-on: ubuntu-latest
32
- name: build (ruby v${{ matrix.ruby }})
33
- strategy:
34
- fail-fast: false
35
- matrix:
36
- ruby:
37
- - "2.7"
38
- - "3.0"
39
- - "3.1"
40
- - "3.2"
41
- - "3.3"
42
- - "3.4"
43
- - "4.0"
44
-
45
- steps:
46
- - uses: actions/checkout@v6
47
- - uses: ruby/setup-ruby@v1
48
- with:
49
- ruby-version: ${{ matrix.ruby }}
50
- bundler-cache: true
51
-
52
- - name: Test
53
- run: bundle exec rake --trace test
54
-
55
- - name: Upload coverage to Codecov
56
- if: always()
57
- uses: codecov/codecov-action@v5
58
- with:
59
- token: ${{ secrets.CODECOV_TOKEN }}
60
- files: ./coverage/.resultset.json
61
- name: codecov-umbrella
62
- verbose: true
63
- fail_ci_if_error: true
data/.gitignore DELETED
@@ -1,44 +0,0 @@
1
- *.gem
2
- *.rbc
3
- /.config
4
- /coverage/
5
- /InstalledFiles
6
- /pkg/
7
- /spec/reports/
8
- /spec/examples.txt
9
- /test/tmp/
10
- /test/version_tmp/
11
- /tmp/
12
-
13
- # Used by dotenv library to load environment variables.
14
- .env
15
-
16
- # Ignore Byebug command history file.
17
- .byebug_history
18
-
19
- ## Documentation cache and generated files:
20
- /.yardoc/
21
- /_yardoc/
22
- /doc/
23
- /rdoc/
24
-
25
- ## Environment normalization:
26
- /.bundle/
27
- /vendor/bundle
28
- /lib/bundler/man/
29
-
30
- # for a library or gem, you might want to ignore these files since the code is
31
- # intended to run in multiple environments; otherwise, check them in:
32
- Gemfile.lock
33
- .ruby-version
34
- .ruby-gemset
35
-
36
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
37
- .rvmrc
38
-
39
- # Used by RuboCop. Remote config files pulled in from inherit_from directive.
40
- # .rubocop-https?--*
41
-
42
- # IDE and system files
43
- .vscode/
44
- **/.DS_STORE
data/.overcommit.yml DELETED
@@ -1,8 +0,0 @@
1
- PreCommit:
2
- RuboCop:
3
- enabled: true
4
- on_warn: fail # Treat all warnings as failures
5
-
6
- CommitMsg:
7
- ALL:
8
- enabled: false
data/.rubocop.yml DELETED
@@ -1,181 +0,0 @@
1
- # The behavior of RuboCop can be controlled via the .rubocop.yml
2
- # configuration file. It makes it possible to enable/disable
3
- # certain cops (checks) and to alter their behavior if they accept
4
- # any parameters. The file can be placed either in your home
5
- # directory or in some project directory.
6
- #
7
- # RuboCop will start looking for the configuration file in the directory
8
- # where the inspected file is and continue its way up to the root directory.
9
- #
10
- # See https://docs.rubocop.org/rubocop/configuration
11
-
12
- plugins:
13
- - rubocop-minitest
14
- - rubocop-packaging
15
- - rubocop-performance
16
-
17
- # General
18
- AllCops:
19
- NewCops: enable
20
- SuggestExtensions: false
21
-
22
- # Layout
23
- Layout/BlockAlignment:
24
- Enabled: true
25
- EnforcedStyleAlignWith: start_of_block
26
-
27
- Layout/ClassStructure:
28
- Enabled: true
29
-
30
- Layout/DotPosition:
31
- Enabled: true
32
- EnforcedStyle: leading
33
-
34
- Layout/FirstArrayElementIndentation:
35
- Enabled: true
36
- EnforcedStyle: consistent
37
-
38
- Layout/FirstHashElementIndentation:
39
- Enabled: true
40
- EnforcedStyle: consistent
41
-
42
- Layout/HashAlignment:
43
- Enabled: true
44
- EnforcedColonStyle: key
45
- EnforcedLastArgumentHashStyle: always_inspect
46
-
47
- Layout/LineEndStringConcatenationIndentation:
48
- Enabled: true
49
- EnforcedStyle: aligned
50
-
51
- Layout/LineLength:
52
- Enabled: true
53
- Max: 120
54
-
55
- Layout/MultilineArrayLineBreaks:
56
- Enabled: true
57
-
58
- Layout/MultilineAssignmentLayout:
59
- Enabled: false
60
-
61
- Layout/MultilineHashKeyLineBreaks:
62
- Enabled: true
63
-
64
- Layout/MultilineMethodArgumentLineBreaks:
65
- Enabled: true
66
-
67
- Layout/MultilineMethodCallIndentation:
68
- Enabled: true
69
- EnforcedStyle: indented_relative_to_receiver
70
-
71
- Layout/MultilineOperationIndentation:
72
- Enabled: true
73
- EnforcedStyle: indented
74
-
75
- Layout/ParameterAlignment:
76
- Enabled: true
77
- EnforcedStyle: with_first_parameter
78
-
79
- Layout/RedundantLineBreak:
80
- Enabled: true
81
- InspectBlocks: false
82
-
83
- Layout/SingleLineBlockChain:
84
- Enabled: false
85
-
86
- Layout/SpaceBeforeBrackets:
87
- Enabled: true
88
-
89
- Layout/TrailingEmptyLines:
90
- Enabled: true
91
- EnforcedStyle: final_newline
92
-
93
- # Lint
94
- Lint/AmbiguousAssignment:
95
- Enabled: true
96
-
97
- Lint/AmbiguousOperatorPrecedence:
98
- Enabled: true
99
-
100
- Lint/AmbiguousRange:
101
- Enabled: true
102
-
103
- # Metrics
104
- Metrics/AbcSize:
105
- Max: 25
106
- Exclude:
107
- - "test/**/*"
108
-
109
- Metrics/ClassLength:
110
- Max: 225
111
- CountAsOne:
112
- - array
113
- - hash
114
- - heredoc
115
- Exclude:
116
- - "test/**/*"
117
-
118
- Metrics/MethodLength:
119
- Max: 20
120
- CountAsOne:
121
- - array
122
- - hash
123
- - heredoc
124
- Exclude:
125
- - "test/**/*"
126
-
127
- # Minitest
128
- Minitest/AssertPredicate:
129
- Enabled: false
130
-
131
- Minitest/EmptyLineBeforeAssertionMethods:
132
- Enabled: false
133
-
134
- Minitest/MultipleAssertions:
135
- Max: 10
136
-
137
- Minitest/RefutePredicate:
138
- Enabled: false
139
-
140
- # Naming
141
- Naming/InclusiveLanguage:
142
- Enabled: true
143
-
144
- Naming/MethodParameterName:
145
- AllowedNames:
146
- - n
147
-
148
- Naming/VariableNumber:
149
- EnforcedStyle: snake_case
150
-
151
- # Style
152
- Style/AndOr:
153
- EnforcedStyle: always
154
-
155
- Style/ArrayCoercion:
156
- Enabled: true
157
-
158
- Style/CollectionCompact:
159
- Enabled: true
160
-
161
- Style/ConstantVisibility:
162
- Enabled: true
163
- IgnoreModules: true
164
-
165
- Style/Documentation:
166
- Exclude:
167
- - "test/**/*"
168
-
169
- Style/DocumentationMethod:
170
- Enabled: true
171
- Exclude:
172
- - "test/**/*"
173
-
174
- Style/SelectByRegexp:
175
- Enabled: false
176
-
177
- Style/SwapValues:
178
- Enabled: true
179
-
180
- Style/TrailingCommaInArrayLiteral:
181
- EnforcedStyleForMultiline: comma
data/.yardopts DELETED
@@ -1 +0,0 @@
1
- -o ./docs - LICENSE
data/Gemfile DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Grift's gem dependencies are defined in grift.gemspec
6
- gemspec
7
-
8
- gem 'rake', '>= 12.0'
9
-
10
- group :development, :test do
11
- gem 'minitest', '~> 5.25'
12
- gem 'minitest-reporters', '~> 1.7'
13
- gem 'simplecov', '~> 0.22'
14
- gem 'simplecov-cobertura', '~> 3.1'
15
- end
16
-
17
- group :development, :lint do
18
- gem 'overcommit', '>= 0.64'
19
- gem 'rubocop', '~> 1.82.1'
20
- gem 'rubocop-minitest', '~> 0.38.2'
21
- gem 'rubocop-packaging', '~> 0.6.0'
22
- gem 'rubocop-performance', '~> 1.26.0'
23
- end
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rake/testtask'
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << 'test'
8
- t.libs << 'lib'
9
- t.test_files = FileList['test/**/*_test.rb']
10
- end
11
-
12
- task default: :test
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'grift'
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require 'irb'
15
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/grift.gemspec DELETED
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lib/grift/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'grift'
7
- spec.version = Grift::VERSION
8
- spec.authors = ['Clark Brown']
9
- spec.email = ['clark@clark-brown.com']
10
-
11
- spec.summary = 'Mocking and spying in MiniTest'
12
- spec.description = "A gem for simple mocking and spying in Ruby's MiniTest framework."
13
- spec.homepage = 'https://github.com/clarkedb/grift'
14
- spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
16
-
17
- spec.metadata = {
18
- 'bug_tracker_uri' => "#{spec.homepage}/issues",
19
- 'changelog_uri' => "#{spec.homepage}/blob/main/CHANGELOG.md",
20
- 'documentation_uri' => spec.homepage.to_s,
21
- 'homepage_uri' => spec.homepage.to_s,
22
- 'rubygems_mfa_required' => 'true',
23
- 'source_code_uri' => spec.homepage.to_s
24
- }
25
-
26
- # Specify which files should be added to the gem when it is released.
27
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
29
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|docs|spec|features)/}) } # rubocop:disable Packaging/GemspecGit
30
- end
31
- spec.bindir = 'exe'
32
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
- spec.require_paths = ['lib']
34
- end