prolog_minitest_matchers 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +224 -20
- data/config.reek +4 -4
- data/lib/prolog_minitest_matchers/matchers/asserters/assert_requires_dry_struct_attribute.rb +27 -0
- data/lib/prolog_minitest_matchers/matchers/asserters/assert_requires_initialize_parameter.rb +9 -53
- data/lib/prolog_minitest_matchers/matchers/asserters/assert_requires_static_call_param.rb +9 -58
- data/lib/prolog_minitest_matchers/matchers/asserters/base_assert_required_parameter.rb +50 -0
- data/lib/prolog_minitest_matchers/matchers/asserters/induce_error.rb +30 -0
- data/lib/prolog_minitest_matchers/matchers/asserters/verify_key_in_hash.rb +32 -0
- data/lib/prolog_minitest_matchers/matchers/requires_dry_struct_attribute.rb +22 -0
- data/lib/prolog_minitest_matchers/version.rb +1 -1
- data/lib/prolog_minitest_matchers.rb +2 -0
- data/prolog_minitest_matchers.gemspec +2 -0
- metadata +28 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81ac10d6ba50c569650a8228fcc14cec39504ee0
|
4
|
+
data.tar.gz: 397e377a53f73bcae18ae287c89b13b3e7728bd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '081234b6d44e0f0ce04f06da3fa88e6b70209d064062c2b693c21ed58ff49060597be8fe0f9fb85d78dacc35c67ad5282d3fdd9f8fcb2bd2be2a84e43037dca0'
|
7
|
+
data.tar.gz: de4ac96c56ca29e0b34be02bde9ea1a4a290916d1ee0dcafb0b1d45f145b5c191e758c6c8e17099fc80fa77f577526f1e459f3622f93e657638dae43ffa80576
|
data/README.md
CHANGED
@@ -2,7 +2,101 @@
|
|
2
2
|
|
3
3
|
This is an evolving collection of MiniTest and MiniTest::Spec custom matchers which we have found useful in our work. As we have built up our applications, we've seen the disparity between *tests* and *assertions* grow; often to a differential of 30% or even more. While some standard MiniTest::Spec expectations involve multiple assertions, we can police our own.
|
4
4
|
|
5
|
-
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'prolog_minitest_matchers'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install prolog_minitest_matchers
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
As of Gem Release 0.3.0, this Gem defines three MiniTest::Spec expectations (and thus three MiniTest asserters) that can be used in your MiniTest code:
|
23
|
+
|
24
|
+
1. `must_require_dry_struct_attribute` (`assert_requires_dry_struct_attribute`);
|
25
|
+
2. `must_require_initialize_parameter` (`assert_requires_initialize_parameter`); and
|
26
|
+
3. `must_require_static_call_param` (`assert_requires_static_call_param`).
|
27
|
+
|
28
|
+
In each case, the expectation (through the implementing asserter) will verify that the parameter expected to cause a failure when omitted **must** exist in the supplied set of full parameters.
|
29
|
+
|
30
|
+
### `must_require_dry_struct_attribute`
|
31
|
+
|
32
|
+
Implemented by asserter `MiniTest::Assertions::AssertRequiresDryStructAttribute`.
|
33
|
+
|
34
|
+
Suppose that you have code such as
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# in foo.rb
|
38
|
+
class Foo < Dry::Types::Struct
|
39
|
+
attribute :foo, Types::Strict::String
|
40
|
+
attribute :bar, Types::Coercible.Int
|
41
|
+
# ...
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
and test code such as
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
# in foo_test.rb
|
49
|
+
|
50
|
+
describe 'Foo' do
|
51
|
+
describe 'initialisation' do
|
52
|
+
describe 'requires parameters for' do
|
53
|
+
let(:params) { { foo: 'some foo', bar: 42 } }
|
54
|
+
|
55
|
+
it 'foo' do
|
56
|
+
params.delete :foo
|
57
|
+
error = expect { Foo.new params }.must_raise KeyError
|
58
|
+
expect(error.message).must_equal "No key :foo in #{params.inspect}!"
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'bar' do
|
62
|
+
# ...
|
63
|
+
end
|
64
|
+
end # describe 'requires parameters for'
|
65
|
+
end # describe 'initialisation'
|
66
|
+
|
67
|
+
# ... other tests
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
That parameter test has two expectations: first, that a `KeyError` will be raised; and second, that that error's message will be as expected. Instead, how about this:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# in foo_test.rb
|
75
|
+
|
76
|
+
describe 'Foo' do
|
77
|
+
describe 'initialisation requires parameters for' do
|
78
|
+
let(:params) { { foo: 'some foo', bar: 42 } }
|
79
|
+
|
80
|
+
it ':foo' do
|
81
|
+
expect(Foo).must_require_dry_struct_attribute params, :foo
|
82
|
+
end
|
83
|
+
|
84
|
+
it ':bar' do
|
85
|
+
expect(Foo).must_require_dry_struct_attribute params, :bar
|
86
|
+
end
|
87
|
+
end # describe 'initialisation requires parameters for'
|
88
|
+
|
89
|
+
# ... other tests
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
Somewhat shorter, yes; more importantly, two much more intention-revealing expectations that are counted as one assertion each. If you ascribe to the widely-used recommendation that each test (`it` block in MiniTest::Spec usage) should test one thing, this will have you wondering "how come my tests and assertions don't match up very well" just that little bit less.
|
94
|
+
|
95
|
+
### `must_require_initialize_parameter`
|
96
|
+
|
97
|
+
Implemented by asserter `MiniTest::Assertions::AssertRequiresInitializeParameter`.
|
98
|
+
|
99
|
+
Again, suppose that you have code such as
|
6
100
|
|
7
101
|
```ruby
|
8
102
|
# in foo.rb
|
@@ -13,7 +107,11 @@ class Foo
|
|
13
107
|
|
14
108
|
# ...
|
15
109
|
end
|
110
|
+
```
|
111
|
+
|
112
|
+
and test code such as
|
16
113
|
|
114
|
+
```ruby
|
17
115
|
# in foo_test.rb
|
18
116
|
|
19
117
|
describe 'Foo' do
|
@@ -37,13 +135,11 @@ describe 'Foo' do
|
|
37
135
|
|
38
136
|
# ... other tests
|
39
137
|
end
|
40
|
-
|
41
138
|
```
|
42
139
|
|
43
|
-
|
140
|
+
As before, that parameter test has two expectations: first, that an `ArgumentError` will be raised; and second, that that error's message will be as expected. Instead, we can now use this:
|
44
141
|
|
45
142
|
```ruby
|
46
|
-
|
47
143
|
# in foo_test.rb
|
48
144
|
|
49
145
|
describe 'Foo' do
|
@@ -65,45 +161,153 @@ describe 'Foo' do
|
|
65
161
|
|
66
162
|
# ... other tests
|
67
163
|
end
|
164
|
+
```
|
165
|
+
|
166
|
+
Again, two much more intention-revealing expectations that are counted as one assertion each.
|
167
|
+
|
168
|
+
### `must_require_static_call_param`
|
68
169
|
|
170
|
+
Implemented by asserter `MiniTest::Assertions::AssertRequiresInitializeParameter`.
|
171
|
+
|
172
|
+
This is useful, not for initialisation in the traditional sense, but for service objects implementing a *class-level* `.call` interface that takes one or more named parameters. You might have code that looks like:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
# in foo.rb
|
176
|
+
class Foo
|
177
|
+
def self.call(foo:, bar:)
|
178
|
+
Foo.new(foo, bar).call
|
179
|
+
end
|
180
|
+
|
181
|
+
def call
|
182
|
+
# ...
|
183
|
+
end
|
184
|
+
|
185
|
+
protected
|
186
|
+
|
187
|
+
def initialize(foo, bar)
|
188
|
+
@foo = massage_initial_foo_with foo
|
189
|
+
@bar = massage_initial_bar_with bar
|
190
|
+
self
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
attr_reader :bar, :foo
|
196
|
+
# ...
|
197
|
+
end
|
69
198
|
```
|
70
199
|
|
71
|
-
|
200
|
+
and test code such as
|
72
201
|
|
73
|
-
|
202
|
+
```ruby
|
203
|
+
# in foo_test.rb
|
74
204
|
|
75
|
-
|
205
|
+
describe 'Foo' do
|
206
|
+
describe 'initialisation' do
|
207
|
+
describe 'requires parameters for' do
|
208
|
+
let(:params) { { foo: 'some foo', bar: 'some bar' } }
|
209
|
+
|
210
|
+
it 'foo' do
|
211
|
+
params.delete :foo
|
212
|
+
error = expect { Foo.new params }.must_raise KeyError
|
213
|
+
expect(error.message).must_equal "No key :foo in #{params.inspect}!"
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'bar' do
|
217
|
+
# ...
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# ... other initialisation tests
|
222
|
+
end
|
223
|
+
|
224
|
+
# ... other tests
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
228
|
+
As before, that parameter test has two expectations: first, that a `KeyError` will be raised; and second, that that error's message will be as expected. We can use this instead:
|
76
229
|
|
77
230
|
```ruby
|
78
|
-
|
231
|
+
# in foo_test.rb
|
232
|
+
|
233
|
+
describe 'Foo' do
|
234
|
+
describe 'initialisation' do
|
235
|
+
describe 'requires parameters for' do
|
236
|
+
let(:params) { { foo: 'some foo', bar: 'some bar' } }
|
237
|
+
|
238
|
+
it 'foo' do
|
239
|
+
expect(Foo).must_require_static_call_param params, :foo
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'bar' do
|
243
|
+
expect(Foo).must_require_static_call_param params, :bar
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# ... other initialisation tests
|
248
|
+
end
|
249
|
+
|
250
|
+
# ... other tests
|
251
|
+
end
|
79
252
|
```
|
80
253
|
|
81
|
-
|
254
|
+
Once again, two much more intention-revealing expectations that are counted as one assertion each.
|
82
255
|
|
83
|
-
|
256
|
+
### Notes on Implementation
|
84
257
|
|
85
|
-
|
258
|
+
Attentive readers may note the strong similarity between these three expectations. They would be right; although the use cases differ in important ways, the implementation details that set each apart from the others have been reduced to a minimum, as inspecting the code itself will reveal. See some room for improvement? Great! Open an issue and let's talk about it!
|
86
259
|
|
87
|
-
$ gem install prolog_minitest_matchers
|
88
260
|
|
89
|
-
##
|
261
|
+
## Errata
|
262
|
+
|
263
|
+
### Reversing MiniTest::Spec expectations does not work (Issue [#1](https://github.com/TheProlog/prolog_minitest_matchers/issues/1))
|
90
264
|
|
91
|
-
|
265
|
+
Ordinarily, MiniTest::Spec matchers provide reversible expectations; that is, expectations can be positive (`must_`) *or* negative (`won't_`). For a trivial example,
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
expect(2 + 2).must_equal 4
|
269
|
+
expect(2 + 2).wont_equal 5
|
270
|
+
```
|
92
271
|
|
93
|
-
|
272
|
+
The same "asserter" code is being exercised, and fails if the asserted condition is false (for `must_equal`) or true (for `wont_equal`).
|
94
273
|
|
95
|
-
|
274
|
+
None of the matchers included as part of Gem Release 0.3.0 (`must_require_dry_struct_attribute`, `must_require_initialize_parameter`, and `must_require_static_call_param`) are reversible in this way. Given the expected use cases of these matchers, this has been judged to be acceptable; the issue has been left open but labelled `wontfix`. (PRs welcome).
|
96
275
|
|
97
276
|
## Development
|
98
277
|
|
99
|
-
After checking out the repo, run `bin/setup` to install dependencies (which as of now must already be installed on your local system). Then, run `bin/rake test` to run the tests
|
278
|
+
After checking out the repo, run `bin/setup` to install dependencies (which as of now must already be installed on your local system). Then, run `bin/rake test` to run the tests, or `bin/rake` to run tests and, if tests are successful, further static-analysis tools ([RuboCop](https://github.com/bbatsov/rubocop), [Flay](https://github.com/seattlerb/flay), [Flog](https://github.com/seattlerb/flog), and [Reek](https://github.com/troessner/reek)).
|
100
279
|
|
101
|
-
To install this
|
280
|
+
To install *your build* of this Gem onto your local machine, run `bin/rake install`. We recommend that you uninstall any previously-installed "official" Gem to increase your confidence that your tests are running against your build.
|
102
281
|
|
103
282
|
## Contributing
|
104
283
|
|
105
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
284
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/TheProlog/prolog_minitest_matchers. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
285
|
+
|
286
|
+
### Process
|
287
|
+
|
288
|
+
If you wish to submit a new feature, such as a new matcher, to the Gem, please [open an issue](https://github.com/TheProlog/prolog_minitest_matchers/issues/new) to discuss your idea with the maintainer and other interested community members. Issue threads are a great place to thrash out the details of what you're trying to accomplish and how your work would affect other code and/or community members. If you need help with something, or aren't sure how to choose between different ideas to accomplish some detail of what you're setting out to do, this is the place to discuss that. There is *no such thing as a stupid question that you don't know the answer to* (once you've researched in your search engine of choice, of course; please do respect people's time and attention).
|
289
|
+
|
290
|
+
The processes for proposing a new feature or a fix to an open bug-report issue are very similar:
|
291
|
+
|
292
|
+
1. Make sure that you have forked this Gem's [repository on GitHub](https://github.com/TheProlog/prolog_minitest_matchers) to your own GitHub account. (If you don't yet have a GitHub account, [join](https://github.com/join?source=header-home); it's free.)
|
293
|
+
2. If you're proposing a new feature, [open an issue](https://github.com/TheProlog/prolog_minitest_matchers/issues/new) as suggested above. If you're addressing an existing issue, *thanks;* you don't need to open a new one.
|
294
|
+
3. Clone *your* copy of the repo to your local development system.
|
295
|
+
4. Create a new Git branch for your work. It's best to give it a reasonably short name that's suggestive of what you're specifically trying to accomplish.
|
296
|
+
1. If you're adding a new feature, such as a new matcher, consider that `dry-struct-attribute` was a more useful branch name for the `must_require_static_call_param` matcher than, say, `my-new-matcher` for (hopefully) obvious reasons.
|
297
|
+
2. If you're adding a fix for an existing issue, say Issue #4172, then a branch name of `issue-4172` is probably perfect.
|
298
|
+
3. **Do not** work on your copy of the `master` branch! Any pull request (see below) that you later submit for changes you've made on `master` will be rejected, and you will be asked to submit your proposed changes on a branch that branches from a commit on the upstream `master` branch.
|
299
|
+
5. Now write great (tests and) code!
|
300
|
+
6. As soon as you have something to show, *even if it's not complete yet* (but it passes what tests you have), [push your branch](https://help.github.com/articles/pushing-to-a-remote/) to *your forked repo* on GitHub and open a [new pull request](https://github.com/TheProlog/prolog_minitest_matchers/compare) ("PR") for *your branch* compared to `master` on the [upstream](https://github.com/TheProlog/prolog_minitest_matchers/) repository. That lets the maintainer and other community members review your code and tests, comment, help out, and so on.
|
301
|
+
7. Continuing with your pull requests, it's usually better if you make small, incremental changes in each commit in a sequence. We (endeavour to) practice [behaviour-driven development](https://en.wikipedia.org/wiki/Behavior-driven_development): write tests for the simplest thing that could possibly work; see the tests fail; then make them pass, commit, and go on to the next simplest thing. Don't get hung up on lots of refactoring until you have code that does everything you want it to do; once you have a legitimately complete green bar, *that's* the time to apply SOLID principles and patterns to DRY things up. Better to have (temporary) duplication than choose the wrong abstraction.
|
302
|
+
|
303
|
+
### Notes on Contributing
|
304
|
+
|
305
|
+
Don't be discouraged if it takes several commits to complete your work and then several more to get everybody agreeing that it's complete and well done. ("Useful" and "worth adding" should have been settled at the issue stage, before you started working on your PR.) That's because…
|
306
|
+
|
307
|
+
When pull requests are *merged* into the `master` branch, they are *squashed* so that all changes are applied to `master` in a single commit. This means that, even if you have a dozen or more commits in your PR where you've been *very* incremental, and even changed direction once or twice, what matters is the final result; not what it took to get there.
|
308
|
+
|
309
|
+
Once your PR has been merged, it's a good idea to pull the upstream `master` branch to your development system (`git pull upstream master`) and then push it to your fork (`git push origin master`). (What? You don't *have* an `upstream` remote as shown by `git remote -v`? Run the command `git remote add upstream https://github.com/TheProlog/prolog_minitest_matchers.git` from your local development directory, and now you do.)
|
106
310
|
|
107
311
|
## License
|
108
312
|
|
109
|
-
The
|
313
|
+
The Gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/config.reek
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry-types'
|
4
|
+
|
5
|
+
require_relative './base_assert_required_parameter'
|
6
|
+
|
7
|
+
module MiniTest
|
8
|
+
# Adding custom assertions to make specs easier to read
|
9
|
+
module Assertions
|
10
|
+
# Actual test logic for `#assert_requires_struct_attribute`.
|
11
|
+
class AssertRequiresDryStructAttribute < BaseAssertRequiredParameter
|
12
|
+
private
|
13
|
+
|
14
|
+
def default_message_for(param_key)
|
15
|
+
"] :#{param_key} is missing in Hash input"
|
16
|
+
end
|
17
|
+
|
18
|
+
def error_class
|
19
|
+
Dry::Types::StructError
|
20
|
+
end
|
21
|
+
|
22
|
+
def error_inducer
|
23
|
+
-> { klass.new params }
|
24
|
+
end
|
25
|
+
end # class MiniTest::Assertions::AssertRequiresDryStructAttribute
|
26
|
+
end
|
27
|
+
end
|
data/lib/prolog_minitest_matchers/matchers/asserters/assert_requires_initialize_parameter.rb
CHANGED
@@ -1,68 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './base_assert_required_parameter'
|
4
|
+
|
3
5
|
module MiniTest
|
4
6
|
# Adding custom assertions to make specs easier to read
|
5
7
|
module Assertions
|
6
8
|
# Actual test logic for `#assert_requires_initialize_parameter`.
|
7
|
-
class AssertRequiresInitializeParameter
|
8
|
-
def initialize(klass, full_params, param_key, message)
|
9
|
-
@klass = klass
|
10
|
-
@full_params = full_params
|
11
|
-
@param_key = param_key
|
12
|
-
@message = initial_message_from(message, param_key)
|
13
|
-
self
|
14
|
-
end
|
15
|
-
|
16
|
-
def call(assert)
|
17
|
-
assert.call(errors_as_expected(save_and_try_to_init), message)
|
18
|
-
end
|
19
|
-
|
9
|
+
class AssertRequiresInitializeParameter < BaseAssertRequiredParameter
|
20
10
|
private
|
21
11
|
|
22
|
-
|
23
|
-
|
24
|
-
def errors_as_expected(errors)
|
25
|
-
errors[:expected]&.message == message
|
26
|
-
end
|
27
|
-
|
28
|
-
def filtered_params
|
29
|
-
full_params.dup.reject { |source_key, _| source_key == param_key }
|
30
|
-
end
|
31
|
-
|
32
|
-
# Running Reek will complain about a :reek:ControlParameter. Protocol is.
|
33
|
-
def initial_message_from(message, param_key)
|
34
|
-
message || "missing keyword: #{param_key}"
|
35
|
-
end
|
36
|
-
|
37
|
-
def save_and_try_to_init
|
38
|
-
verify_param_in_list
|
39
|
-
save_and_delete_param_before { |params| try_to_init params }
|
40
|
-
end
|
41
|
-
|
42
|
-
def save_and_delete_param_before
|
43
|
-
yield filtered_params
|
44
|
-
end
|
45
|
-
|
46
|
-
def try_to_init(params)
|
47
|
-
expected_error = nil
|
48
|
-
begin
|
49
|
-
klass.new params
|
50
|
-
rescue ArgumentError => error
|
51
|
-
expected_error = error
|
52
|
-
end
|
53
|
-
{ expected: expected_error }
|
54
|
-
end
|
55
|
-
|
56
|
-
def verify_param_in_list
|
57
|
-
raise KeyError, no_param_message unless param_in_full_list?
|
12
|
+
def default_message_for(param_key)
|
13
|
+
"missing keyword: #{param_key}"
|
58
14
|
end
|
59
15
|
|
60
|
-
def
|
61
|
-
|
16
|
+
def error_class
|
17
|
+
ArgumentError
|
62
18
|
end
|
63
19
|
|
64
|
-
def
|
65
|
-
|
20
|
+
def error_inducer
|
21
|
+
-> { klass.new params }
|
66
22
|
end
|
67
23
|
end # class MiniTest::Assertions::AssertRequiresInitializeParameter
|
68
24
|
end
|
@@ -1,73 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './base_assert_required_parameter'
|
4
|
+
|
3
5
|
module MiniTest
|
4
6
|
# Adding custom assertions to make specs easier to read
|
5
7
|
module Assertions
|
6
8
|
# Actual test logic for `#assert_requires_static_call_paramr`.
|
7
|
-
class AssertRequiresStaticCallParam
|
8
|
-
def initialize(klass, full_params, param_key, message)
|
9
|
-
@klass = klass
|
10
|
-
@full_params = full_params
|
11
|
-
@param_key = param_key
|
12
|
-
@message = initial_message_from(message, param_key)
|
13
|
-
self
|
14
|
-
end
|
15
|
-
|
16
|
-
def call(assert)
|
17
|
-
verify_param_in_list(assert)
|
18
|
-
assert.call(errors_as_expected(save_and_try_to_call), message)
|
19
|
-
end
|
20
|
-
|
9
|
+
class AssertRequiresStaticCallParam < BaseAssertRequiredParameter
|
21
10
|
private
|
22
11
|
|
23
|
-
|
24
|
-
|
25
|
-
def errors_as_expected(errors)
|
26
|
-
errors[:expected]&.message == message
|
27
|
-
end
|
28
|
-
|
29
|
-
def filtered_params
|
30
|
-
full_params.dup.reject { |source_key, _| source_key == param_key }
|
31
|
-
end
|
32
|
-
|
33
|
-
# Running Reek will complain about a :reek:ControlParameter. Protocol is.
|
34
|
-
def initial_message_from(message, param_key)
|
35
|
-
message || "missing keyword: #{param_key}"
|
36
|
-
end
|
37
|
-
|
38
|
-
def key_not_found_message
|
39
|
-
"Key :#{param_key} not found in #{full_params}!"
|
40
|
-
end
|
41
|
-
|
42
|
-
def save_and_try_to_call
|
43
|
-
save_and_delete_param_before { |params| try_to_call params }
|
44
|
-
# saved_item = full_params[param_key]
|
45
|
-
# full_params.delete param_key
|
46
|
-
# errors = try_to_call
|
47
|
-
# full_params[param_key] = saved_item
|
48
|
-
# errors
|
49
|
-
end
|
50
|
-
|
51
|
-
def try_to_call(params)
|
52
|
-
expected_error = nil
|
53
|
-
begin
|
54
|
-
klass.call params
|
55
|
-
rescue ArgumentError => error
|
56
|
-
expected_error = error
|
57
|
-
end
|
58
|
-
{ expected: expected_error }
|
59
|
-
end
|
60
|
-
|
61
|
-
def save_and_delete_param_before
|
62
|
-
yield filtered_params
|
12
|
+
def default_message_for(param_key)
|
13
|
+
"missing keyword: #{param_key}"
|
63
14
|
end
|
64
15
|
|
65
|
-
def
|
66
|
-
|
16
|
+
def error_class
|
17
|
+
ArgumentError
|
67
18
|
end
|
68
19
|
|
69
|
-
def
|
70
|
-
|
20
|
+
def error_inducer
|
21
|
+
-> { klass.call params }
|
71
22
|
end
|
72
23
|
end # class MiniTest::Assertions::AssertRequiresStaticCallParam
|
73
24
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './induce_error'
|
4
|
+
require_relative './verify_key_in_hash'
|
5
|
+
|
6
|
+
module MiniTest
|
7
|
+
# Adding custom assertions to make specs easier to read
|
8
|
+
module Assertions
|
9
|
+
# Base assertion class to verify error raised when parameter omitted.
|
10
|
+
class BaseAssertRequiredParameter
|
11
|
+
def initialize(klass, params, param_key, message)
|
12
|
+
message ||= default_message_for(param_key)
|
13
|
+
@params = Internals.hash_without_key(params, param_key)
|
14
|
+
@klass = klass
|
15
|
+
@param_key = param_key
|
16
|
+
@message = message
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(assert)
|
21
|
+
assert.call(correct_error_message?, message)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :params, :klass, :message, :param_key
|
27
|
+
|
28
|
+
def actual_error_message
|
29
|
+
errors[:expected]&.message
|
30
|
+
end
|
31
|
+
|
32
|
+
def correct_error_message?
|
33
|
+
actual_error_message.match message
|
34
|
+
end
|
35
|
+
|
36
|
+
def errors
|
37
|
+
InduceError.call error_class: error_class, inducer: error_inducer
|
38
|
+
end
|
39
|
+
|
40
|
+
# Methods that neither affect nor are affected by instance state.
|
41
|
+
module Internals
|
42
|
+
def self.hash_without_key(hash, key)
|
43
|
+
VerifyKeyInHash.call hash, key
|
44
|
+
hash.dup.reject { |source_key, _| source_key == key }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
private_constant :Internals
|
48
|
+
end # class MiniTest::Assertions::BaseAssertRequiredParameter
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Attempt to induce a specified failure, and report if successful or not.
|
4
|
+
class InduceError
|
5
|
+
def self.call(error_class:, inducer:)
|
6
|
+
InduceError.new(error_class, inducer).call
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
expected_error = nil
|
11
|
+
begin
|
12
|
+
inducer.call
|
13
|
+
rescue error_class => error
|
14
|
+
expected_error = error
|
15
|
+
end
|
16
|
+
{ expected: expected_error }
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def initialize(error_class, inducer)
|
22
|
+
@error_class = error_class
|
23
|
+
@inducer = inducer
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :error_class, :inducer
|
30
|
+
end # class InduceError
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Used to validate incoming parameter hash.
|
4
|
+
class VerifyKeyInHash
|
5
|
+
def self.call(hash, key)
|
6
|
+
VerifyKeyInHash.new(hash, key).call
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
raise KeyError, no_key_message unless key_in_hash?
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def initialize(hash, key)
|
16
|
+
@hash = hash
|
17
|
+
@key = key
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :hash, :key
|
24
|
+
|
25
|
+
def key_in_hash?
|
26
|
+
hash.key? key
|
27
|
+
end
|
28
|
+
|
29
|
+
def no_key_message
|
30
|
+
"No key :#{key} in #{hash}!"
|
31
|
+
end
|
32
|
+
end # class VerifyKeyInHash
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/spec'
|
4
|
+
|
5
|
+
require_relative './asserters/assert_requires_dry_struct_attribute'
|
6
|
+
|
7
|
+
module MiniTest
|
8
|
+
# Adding custom assertions to make specs easier to read
|
9
|
+
module Assertions
|
10
|
+
def assert_requires_dry_struct_attribute(klass, full_params, param_key,
|
11
|
+
message = nil)
|
12
|
+
AssertRequiresDryStructAttribute.new(klass, full_params, param_key,
|
13
|
+
message).call(method(:assert))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Make it available to MiniTest::Spec
|
18
|
+
module Expectations
|
19
|
+
infect_an_assertion :assert_requires_dry_struct_attribute,
|
20
|
+
:must_require_dry_struct_attribute, :reverse
|
21
|
+
end
|
22
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'prolog_minitest_matchers/version'
|
4
|
+
require 'prolog_minitest_matchers/matchers/requires_dry_struct_attribute'
|
4
5
|
require 'prolog_minitest_matchers/matchers/requires_initialize_parameter'
|
6
|
+
require 'prolog_minitest_matchers/matchers/requires_static_call_param'
|
5
7
|
|
6
8
|
# Module to satisfy Gem requirements; nothing inside but version-spec constant.
|
7
9
|
module PrologMinitestMatchers
|
@@ -27,6 +27,8 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
+
spec.add_dependency "dry-types", "~> 0.7", ">= 0.7.2"
|
31
|
+
|
30
32
|
spec.add_development_dependency "bundler", "~> 1.12", ">= 1.12.5"
|
31
33
|
spec.add_development_dependency "rake", "~> 11.2", ">= 11.2.2"
|
32
34
|
spec.add_development_dependency "minitest", "~> 5.9", ">= 5.9.0"
|
metadata
CHANGED
@@ -1,15 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prolog_minitest_matchers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Dickey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-types
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.7'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.7.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.7'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.7.2
|
13
33
|
- !ruby/object:Gem::Dependency
|
14
34
|
name: bundler
|
15
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -187,8 +207,13 @@ files:
|
|
187
207
|
- Rakefile
|
188
208
|
- config.reek
|
189
209
|
- lib/prolog_minitest_matchers.rb
|
210
|
+
- lib/prolog_minitest_matchers/matchers/asserters/assert_requires_dry_struct_attribute.rb
|
190
211
|
- lib/prolog_minitest_matchers/matchers/asserters/assert_requires_initialize_parameter.rb
|
191
212
|
- lib/prolog_minitest_matchers/matchers/asserters/assert_requires_static_call_param.rb
|
213
|
+
- lib/prolog_minitest_matchers/matchers/asserters/base_assert_required_parameter.rb
|
214
|
+
- lib/prolog_minitest_matchers/matchers/asserters/induce_error.rb
|
215
|
+
- lib/prolog_minitest_matchers/matchers/asserters/verify_key_in_hash.rb
|
216
|
+
- lib/prolog_minitest_matchers/matchers/requires_dry_struct_attribute.rb
|
192
217
|
- lib/prolog_minitest_matchers/matchers/requires_initialize_parameter.rb
|
193
218
|
- lib/prolog_minitest_matchers/matchers/requires_static_call_param.rb
|
194
219
|
- lib/prolog_minitest_matchers/version.rb
|
@@ -219,3 +244,4 @@ signing_key:
|
|
219
244
|
specification_version: 4
|
220
245
|
summary: Custom Minitest matcher(s) we've developed for our own use.
|
221
246
|
test_files: []
|
247
|
+
has_rdoc:
|