matchi 4.0.0 → 4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.md +1 -1
- data/README.md +76 -15
- data/lib/matchi.rb +300 -0
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b3579155f166f952bb90f1a6ea79c7fd99d7ec17c81782e36d0a7d79ed58596
|
4
|
+
data.tar.gz: acfda4f687384be25a015fa71a709c7101f69dbb7a0246d2c994eecadc959f11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11593bee0850470dda340821820aba5cdf82b99752f01d4de54de0849ca935a520d1d44500abd1d4415809ea36b6a15b4603fd84d198797b4ab81e9af0185551
|
7
|
+
data.tar.gz: 8bb333121816652c5b3ca015df89edf962824453274fb89be666f21f313921de65d16b7901a52250076b66c47fe393e9335c31024c90a6465678ff537bf3846f
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -63,6 +63,67 @@ A **Matchi** matcher is a simple Ruby object that follows these requirements:
|
|
63
63
|
2. Optionally, it may implement:
|
64
64
|
- `to_s`: Returns a human-readable description of the match criteria
|
65
65
|
|
66
|
+
### Using Matchers
|
67
|
+
|
68
|
+
There are two main ways to use Matchi matchers:
|
69
|
+
|
70
|
+
#### 1. Direct Class Instantiation
|
71
|
+
|
72
|
+
You can create matchers directly from their classes:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
matcher = Matchi::Eq.new("foo")
|
76
|
+
matcher.match? { "foo" } # => true
|
77
|
+
|
78
|
+
matcher = Matchi::BeWithin.new(0.5).of(3.0)
|
79
|
+
matcher.match? { 3.2 } # => true
|
80
|
+
```
|
81
|
+
|
82
|
+
#### 2. Helper Methods via Module Inclusion
|
83
|
+
|
84
|
+
For a more expressive and readable syntax, you can include or extend the `Matchi` module to get access to helper methods:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
# Including in a class for instance methods
|
88
|
+
class MyTestFramework
|
89
|
+
include Matchi
|
90
|
+
|
91
|
+
def test_equality
|
92
|
+
# Helper methods available as instance methods
|
93
|
+
matcher = eq("foo")
|
94
|
+
assert(matcher.match? { "foo" })
|
95
|
+
|
96
|
+
# Method chaining works too
|
97
|
+
assert(change(@array, :length).by(1).match? { @array << 1 })
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Extending a class for class methods
|
102
|
+
class MyAssertions
|
103
|
+
extend Matchi
|
104
|
+
|
105
|
+
def self.assert_equals(expected, actual)
|
106
|
+
eq(expected).match? { actual }
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.assert_within_range(expected, delta, actual)
|
110
|
+
be_within(delta).of(expected).match? { actual }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
Available helper methods correspond to the built-in matchers:
|
116
|
+
- `eq` / `eql` - For equivalence matching
|
117
|
+
- `be` / `equal` - For identity matching
|
118
|
+
- `be_within` - For delta comparisons
|
119
|
+
- `match` - For regular expression matching
|
120
|
+
- `change` - For state changes
|
121
|
+
- `be_true`, `be_false`, `be_nil` - For state verification
|
122
|
+
- `be_an_instance_of` - For exact type matching
|
123
|
+
- `be_a_kind_of` - For type hierarchy matching
|
124
|
+
- `satisfy` - For custom block-based matching
|
125
|
+
- Dynamic predicate matchers (`be_*` and `have_*`)
|
126
|
+
|
66
127
|
### Built-in matchers
|
67
128
|
|
68
129
|
Here is the collection of generic matchers.
|
@@ -72,15 +133,15 @@ Here is the collection of generic matchers.
|
|
72
133
|
##### `Be`
|
73
134
|
Checks for object identity using Ruby's `equal?` method.
|
74
135
|
```ruby
|
75
|
-
Matchi::Be.new(:foo).match? { :foo }
|
76
|
-
Matchi::Be.new("test").match? { "test" }
|
136
|
+
Matchi::Be.new(:foo).match? { :foo } # => true (same object)
|
137
|
+
Matchi::Be.new("test").match? { "test" } # => false (different objects)
|
77
138
|
```
|
78
139
|
|
79
140
|
##### `Eq`
|
80
141
|
Verifies object equivalence using Ruby's `eql?` method.
|
81
142
|
```ruby
|
82
|
-
Matchi::Eq.new("foo").match? { "foo" }
|
83
|
-
Matchi::Eq.new([1, 2]).match? { [1, 2] }
|
143
|
+
Matchi::Eq.new("foo").match? { "foo" } # => true (equivalent content)
|
144
|
+
Matchi::Eq.new([1, 2]).match? { [1, 2] } # => true (equivalent arrays)
|
84
145
|
```
|
85
146
|
|
86
147
|
#### Type and Class Matchers
|
@@ -113,8 +174,8 @@ Matchi::Match.new(/^foo/).match? { "barfoo" } # => false
|
|
113
174
|
##### `Satisfy`
|
114
175
|
Provides custom matching through a block.
|
115
176
|
```ruby
|
116
|
-
Matchi::Satisfy.new { |x| x
|
117
|
-
Matchi::Satisfy.new { |x| x.start_with?("test") }.match? { "test_file" }
|
177
|
+
Matchi::Satisfy.new { |x| x.positive? && x < 10 }.match? { 5 } # => true
|
178
|
+
Matchi::Satisfy.new { |x| x.start_with?("test") }.match? { "test_file" } # => true
|
118
179
|
```
|
119
180
|
|
120
181
|
#### State Change Matchers
|
@@ -125,31 +186,31 @@ Verifies state changes in objects with multiple variation methods:
|
|
125
186
|
###### Basic Change
|
126
187
|
```ruby
|
127
188
|
array = []
|
128
|
-
Matchi::Change.new(array, :length).by(2).match? { array.push(1, 2) }
|
189
|
+
Matchi::Change.new(array, :length).by(2).match? { array.push(1, 2) } # => true
|
129
190
|
```
|
130
191
|
|
131
192
|
###### Minimum Change
|
132
193
|
```ruby
|
133
194
|
counter = 0
|
134
|
-
Matchi::Change.new(counter, :to_i).by_at_least(2).match? { counter += 3 }
|
195
|
+
Matchi::Change.new(counter, :to_i).by_at_least(2).match? { counter += 3 } # => true
|
135
196
|
```
|
136
197
|
|
137
198
|
###### Maximum Change
|
138
199
|
```ruby
|
139
200
|
value = 10
|
140
|
-
Matchi::Change.new(value, :to_i).by_at_most(5).match? { value += 3 }
|
201
|
+
Matchi::Change.new(value, :to_i).by_at_most(5).match? { value += 3 } # => true
|
141
202
|
```
|
142
203
|
|
143
204
|
###### From-To Change
|
144
205
|
```ruby
|
145
206
|
string = "hello"
|
146
|
-
Matchi::Change.new(string, :upcase).from("hello").to("HELLO").match? { string.upcase! }
|
207
|
+
Matchi::Change.new(string, :upcase).from("hello").to("HELLO").match? { string.upcase! } # => true
|
147
208
|
```
|
148
209
|
|
149
210
|
###### To-Only Change
|
150
211
|
```ruby
|
151
212
|
number = 1
|
152
|
-
Matchi::Change.new(number, :to_i).to(5).match? { number = 5 }
|
213
|
+
Matchi::Change.new(number, :to_i).to(5).match? { number = 5 } # => true
|
153
214
|
```
|
154
215
|
|
155
216
|
#### Numeric Matchers
|
@@ -166,8 +227,8 @@ Matchi::BeWithin.new(5).of(100).match? { 98 } # => true
|
|
166
227
|
##### `RaiseException`
|
167
228
|
Verifies that code raises specific exceptions.
|
168
229
|
```ruby
|
169
|
-
Matchi::RaiseException.new(ArgumentError).match? { raise ArgumentError }
|
170
|
-
Matchi::RaiseException.new(NameError).match? { undefined_variable }
|
230
|
+
Matchi::RaiseException.new(ArgumentError).match? { raise ArgumentError } # => true
|
231
|
+
Matchi::RaiseException.new(NameError).match? { undefined_variable } # => true
|
171
232
|
```
|
172
233
|
|
173
234
|
##### `Predicate`
|
@@ -181,7 +242,7 @@ Matchi::Predicate.new(:be_nil).match? { nil } # => true (calls nil?)
|
|
181
242
|
|
182
243
|
###### Using `have_` prefix
|
183
244
|
```ruby
|
184
|
-
Matchi::Predicate.new(:have_key, :foo).match? { { foo: 42 } }
|
245
|
+
Matchi::Predicate.new(:have_key, :foo).match? { { foo: 42 } } # => true (calls has_key?)
|
185
246
|
```
|
186
247
|
|
187
248
|
### Custom matchers
|
@@ -288,7 +349,7 @@ This is why Matchi's built-in matchers are implemented with this security consid
|
|
288
349
|
```ruby
|
289
350
|
# Implementation in Matchi::Eq
|
290
351
|
def match?
|
291
|
-
@expected.eql?(yield)
|
352
|
+
@expected.eql?(yield) # Expected value controls the comparison
|
292
353
|
end
|
293
354
|
```
|
294
355
|
|
data/lib/matchi.rb
CHANGED
@@ -2,8 +2,308 @@
|
|
2
2
|
|
3
3
|
# A collection of damn simple expectation matchers.
|
4
4
|
#
|
5
|
+
# The Matchi module provides two ways to create and use matchers:
|
6
|
+
#
|
7
|
+
# 1. Direct instantiation through classes:
|
8
|
+
# ```ruby
|
9
|
+
# matcher = Matchi::Eq.new("foo")
|
10
|
+
# matcher.match? { "foo" } # => true
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# 2. Helper methods by including/extending the Matchi module:
|
14
|
+
# ```ruby
|
15
|
+
# # Via include in a class
|
16
|
+
# class MyTestFramework
|
17
|
+
# include Matchi
|
18
|
+
#
|
19
|
+
# def test_something
|
20
|
+
# # Helpers are now available as instance methods
|
21
|
+
# matcher = eq("foo")
|
22
|
+
# matcher.match? { "foo" } # => true
|
23
|
+
#
|
24
|
+
# # Multiple helpers can be chained
|
25
|
+
# change(@array, :length).by(1).match? { @array << 1 }
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # Or via extend for direct usage
|
30
|
+
# class MyOtherFramework
|
31
|
+
# extend Matchi
|
32
|
+
#
|
33
|
+
# def self.assert_equals(expected, actual)
|
34
|
+
# eq(expected).match? { actual }
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# ```
|
38
|
+
#
|
39
|
+
# The helper method approach provides a more readable and fluent interface,
|
40
|
+
# making tests easier to write and understand. It's particularly useful when
|
41
|
+
# integrating with testing frameworks or creating custom assertions.
|
42
|
+
#
|
43
|
+
# Available helpers include:
|
44
|
+
# - eq/eql : Equivalence matching
|
45
|
+
# - be/equal : Identity matching
|
46
|
+
# - be_within : Delta comparison
|
47
|
+
# - match : Regular expression matching
|
48
|
+
# - change : State change verification
|
49
|
+
# - be_true/be_false/be_nil : State verification
|
50
|
+
# - be_an_instance_of : Exact type matching
|
51
|
+
# - be_a_kind_of : Type hierarchy matching
|
52
|
+
# - satisfy : Custom block-based matching
|
53
|
+
# - be_* & have_* : Dynamic predicate matching
|
54
|
+
#
|
5
55
|
# @api public
|
6
56
|
module Matchi
|
57
|
+
# Equivalence matcher
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# matcher = eq("foo")
|
61
|
+
# matcher.match? { "foo" } # => true
|
62
|
+
# matcher.match? { "bar" } # => false
|
63
|
+
#
|
64
|
+
# @param expected [#eql?] An expected equivalent object.
|
65
|
+
#
|
66
|
+
# @return [#match?] An equivalence matcher.
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def eq(expected)
|
70
|
+
::Matchi::Eq.new(expected)
|
71
|
+
end
|
72
|
+
|
73
|
+
alias eql eq
|
74
|
+
|
75
|
+
# Identity matcher
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# object = "foo"
|
79
|
+
# matcher = be(object)
|
80
|
+
# matcher.match? { object } # => true
|
81
|
+
# matcher.match? { "foo" } # => false
|
82
|
+
#
|
83
|
+
# @param expected [#equal?] The expected identical object.
|
84
|
+
#
|
85
|
+
# @return [#match?] An identity matcher.
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
def be(expected)
|
89
|
+
::Matchi::Be.new(expected)
|
90
|
+
end
|
91
|
+
|
92
|
+
alias equal be
|
93
|
+
|
94
|
+
# Comparisons matcher
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# matcher = be_within(1).of(41)
|
98
|
+
# matcher.match? { 42 } # => true
|
99
|
+
# matcher.match? { 43 } # => false
|
100
|
+
#
|
101
|
+
# @param delta [Numeric] A numeric value.
|
102
|
+
#
|
103
|
+
# @return [#match?] A comparison matcher.
|
104
|
+
#
|
105
|
+
# @api public
|
106
|
+
def be_within(delta)
|
107
|
+
::Matchi::BeWithin.new(delta)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Regular expressions matcher
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# matcher = match(/^foo$/)
|
114
|
+
# matcher.match? { "foo" } # => true
|
115
|
+
# matcher.match? { "bar" } # => false
|
116
|
+
#
|
117
|
+
# @param expected [#match] A regular expression.
|
118
|
+
#
|
119
|
+
# @return [#match?] A regular expression matcher.
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
def match(expected)
|
123
|
+
::Matchi::Match.new(expected)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Expecting errors matcher
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
# matcher = raise_exception(NameError)
|
130
|
+
# matcher.match? { Boom } # => true
|
131
|
+
# matcher.match? { true } # => false
|
132
|
+
#
|
133
|
+
# @param expected [Exception, #to_s] The expected exception name.
|
134
|
+
#
|
135
|
+
# @return [#match?] An error matcher.
|
136
|
+
#
|
137
|
+
# @api public
|
138
|
+
def raise_exception(expected)
|
139
|
+
::Matchi::RaiseException.new(expected)
|
140
|
+
end
|
141
|
+
|
142
|
+
# True matcher
|
143
|
+
#
|
144
|
+
# @example
|
145
|
+
# matcher = be_true
|
146
|
+
# matcher.match? { true } # => true
|
147
|
+
# matcher.match? { false } # => false
|
148
|
+
# matcher.match? { nil } # => false
|
149
|
+
# matcher.match? { 4 } # => false
|
150
|
+
#
|
151
|
+
# @return [#match?] A `true` matcher.
|
152
|
+
#
|
153
|
+
# @api public
|
154
|
+
def be_true
|
155
|
+
be(true)
|
156
|
+
end
|
157
|
+
|
158
|
+
# False matcher
|
159
|
+
#
|
160
|
+
# @example
|
161
|
+
# matcher = be_false
|
162
|
+
# matcher.match? { false } # => true
|
163
|
+
# matcher.match? { true } # => false
|
164
|
+
# matcher.match? { nil } # => false
|
165
|
+
# matcher.match? { 4 } # => false
|
166
|
+
#
|
167
|
+
# @return [#match?] A `false` matcher.
|
168
|
+
#
|
169
|
+
# @api public
|
170
|
+
def be_false
|
171
|
+
be(false)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Nil matcher
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# matcher = be_nil
|
178
|
+
# matcher.match? { nil } # => true
|
179
|
+
# matcher.match? { false } # => false
|
180
|
+
# matcher.match? { true } # => false
|
181
|
+
# matcher.match? { 4 } # => false
|
182
|
+
#
|
183
|
+
# @return [#match?] A `nil` matcher.
|
184
|
+
#
|
185
|
+
# @api public
|
186
|
+
def be_nil
|
187
|
+
be(nil)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Type/class matcher
|
191
|
+
#
|
192
|
+
# Verifies exact class matching (no inheritance).
|
193
|
+
#
|
194
|
+
# @example
|
195
|
+
# matcher = be_an_instance_of(String)
|
196
|
+
# matcher.match? { "foo" } # => true
|
197
|
+
# matcher.match? { 4 } # => false
|
198
|
+
#
|
199
|
+
# @param expected [Class, #to_s] The expected class name.
|
200
|
+
#
|
201
|
+
# @return [#match?] A type/class matcher.
|
202
|
+
#
|
203
|
+
# @api public
|
204
|
+
def be_an_instance_of(expected)
|
205
|
+
::Matchi::BeAnInstanceOf.new(expected)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Type/class matcher
|
209
|
+
#
|
210
|
+
# Verifies class inheritance and module inclusion.
|
211
|
+
#
|
212
|
+
# @example
|
213
|
+
# matcher = be_a_kind_of(Numeric)
|
214
|
+
# matcher.match? { 42 } # => true (Integer inherits from Numeric)
|
215
|
+
# matcher.match? { 42.0 } # => true (Float inherits from Numeric)
|
216
|
+
#
|
217
|
+
# @param expected [Class, #to_s] The expected class name.
|
218
|
+
#
|
219
|
+
# @return [#match?] A type/class matcher.
|
220
|
+
#
|
221
|
+
# @api public
|
222
|
+
def be_a_kind_of(expected)
|
223
|
+
::Matchi::BeAKindOf.new(expected)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Change matcher
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
# object = []
|
230
|
+
# matcher = change(object, :length).by(1)
|
231
|
+
# matcher.match? { object << 1 } # => true
|
232
|
+
#
|
233
|
+
# object = []
|
234
|
+
# matcher = change(object, :length).by_at_least(1)
|
235
|
+
# matcher.match? { object << 1 } # => true
|
236
|
+
#
|
237
|
+
# object = []
|
238
|
+
# matcher = change(object, :length).by_at_most(1)
|
239
|
+
# matcher.match? { object << 1 } # => true
|
240
|
+
#
|
241
|
+
# object = "foo"
|
242
|
+
# matcher = change(object, :to_s).from("foo").to("FOO")
|
243
|
+
# matcher.match? { object.upcase! } # => true
|
244
|
+
#
|
245
|
+
# object = "foo"
|
246
|
+
# matcher = change(object, :to_s).to("FOO")
|
247
|
+
# matcher.match? { object.upcase! } # => true
|
248
|
+
#
|
249
|
+
# @param object [#object_id] An object.
|
250
|
+
# @param method [Symbol] The name of a method.
|
251
|
+
#
|
252
|
+
# @return [#match?] A change matcher.
|
253
|
+
#
|
254
|
+
# @api public
|
255
|
+
def change(object, method, ...)
|
256
|
+
::Matchi::Change.new(object, method, ...)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Satisfy matcher
|
260
|
+
#
|
261
|
+
# @example
|
262
|
+
# matcher = satisfy { |value| value == 42 }
|
263
|
+
# matcher.match? { 42 } # => true
|
264
|
+
#
|
265
|
+
# @yield [value] A block that defines the satisfaction criteria
|
266
|
+
# @yieldparam value The value to test
|
267
|
+
# @yieldreturn [Boolean] true if the value satisfies the criteria
|
268
|
+
#
|
269
|
+
# @return [#match?] A satisfy matcher.
|
270
|
+
#
|
271
|
+
# @api public
|
272
|
+
def satisfy(&)
|
273
|
+
::Matchi::Satisfy.new(&)
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
# Predicate matcher, or default method missing behavior.
|
279
|
+
#
|
280
|
+
# @example Empty predicate matcher
|
281
|
+
# matcher = be_empty
|
282
|
+
# matcher.match? { [] } # => true
|
283
|
+
# matcher.match? { [4] } # => false
|
284
|
+
def method_missing(name, ...)
|
285
|
+
return super unless predicate_matcher_name?(name)
|
286
|
+
|
287
|
+
::Matchi::Predicate.new(name, ...)
|
288
|
+
end
|
289
|
+
|
290
|
+
# :nocov:
|
291
|
+
|
292
|
+
# Hook method to return whether the obj can respond to id method or not.
|
293
|
+
def respond_to_missing?(name, include_private = false)
|
294
|
+
predicate_matcher_name?(name) || super
|
295
|
+
end
|
296
|
+
|
297
|
+
# :nocov:
|
298
|
+
|
299
|
+
# Predicate matcher name detector.
|
300
|
+
#
|
301
|
+
# @param name [Array, Symbol] The name of a potential predicate matcher.
|
302
|
+
#
|
303
|
+
# @return [Boolean] Indicates if it is a predicate matcher name or not.
|
304
|
+
def predicate_matcher_name?(name)
|
305
|
+
name.start_with?("be_", "have_") && !name.end_with?("!", "?")
|
306
|
+
end
|
7
307
|
end
|
8
308
|
|
9
309
|
Dir[File.join(File.dirname(__FILE__), "matchi", "*.rb")].each do |fname|
|
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: matchi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
|
+
autorequire:
|
8
9
|
bindir: bin
|
9
10
|
cert_chain: []
|
10
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-31 00:00:00.000000000 Z
|
11
12
|
dependencies: []
|
12
13
|
description: "Collection of expectation matchers for Rubyists \U0001F939"
|
13
14
|
email: contact@cyril.email
|
@@ -40,6 +41,7 @@ licenses:
|
|
40
41
|
- MIT
|
41
42
|
metadata:
|
42
43
|
rubygems_mfa_required: 'true'
|
44
|
+
post_install_message:
|
43
45
|
rdoc_options: []
|
44
46
|
require_paths:
|
45
47
|
- lib
|
@@ -47,14 +49,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
49
|
requirements:
|
48
50
|
- - ">="
|
49
51
|
- !ruby/object:Gem::Version
|
50
|
-
version: 3.
|
52
|
+
version: 3.1.0
|
51
53
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
54
|
requirements:
|
53
55
|
- - ">="
|
54
56
|
- !ruby/object:Gem::Version
|
55
57
|
version: '0'
|
56
58
|
requirements: []
|
57
|
-
rubygems_version: 3.
|
59
|
+
rubygems_version: 3.3.27
|
60
|
+
signing_key:
|
58
61
|
specification_version: 4
|
59
62
|
summary: "Collection of expectation matchers for Rubyists \U0001F939"
|
60
63
|
test_files: []
|