matchi 4.1.0 → 4.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 +4 -4
- data/LICENSE.md +1 -1
- data/README.md +148 -219
- data/lib/matchi/be.rb +58 -16
- data/lib/matchi/be_a_kind_of.rb +87 -34
- data/lib/matchi/be_an_instance_of.rb +74 -62
- data/lib/matchi/be_within.rb +125 -14
- data/lib/matchi/change/by.rb +99 -22
- data/lib/matchi/change/by_at_least.rb +118 -21
- data/lib/matchi/change/by_at_most.rb +132 -22
- data/lib/matchi/change/from/to.rb +92 -25
- data/lib/matchi/change/from.rb +31 -1
- data/lib/matchi/change/to.rb +72 -23
- data/lib/matchi/change.rb +119 -45
- data/lib/matchi/eq.rb +58 -16
- data/lib/matchi/match.rb +56 -16
- data/lib/matchi/predicate.rb +122 -33
- data/lib/matchi/raise_exception.rb +89 -22
- data/lib/matchi/satisfy.rb +87 -16
- data/lib/matchi.rb +51 -297
- metadata +17 -7
- data/lib/matchi/be_within/of.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eed23fa6963893d67a2cc6042f1f977f4fb0012013d53775e142bf92e1333a6d
|
4
|
+
data.tar.gz: 97c4f82fa65c4d46af8babc966005d8a2db81b85b26987cf5fe3444f14ef6a25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 271a0262764b04a77af4d149485395a1cf4d617c62e65dc6fab2388ad5873f3092ce08a9c273e836780623122599603a2a365c199f51ba20a2cda0f6a94f4867
|
7
|
+
data.tar.gz: baa9876cb229024e218d1ee07d4913805f9b64eaa49fab23e76b01d2fdab1f05ba4b952920cb127a3e3fe8c71990a7653d7513828e68faa056632ce0619854de
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -2,312 +2,235 @@
|
|
2
2
|
|
3
3
|
[](https://github.com/fixrb/matchi/tags)
|
4
4
|
[](https://rubydoc.info/github/fixrb/matchi/main)
|
5
|
-
[](https://github.com/fixrb/matchi/actions?query=workflow%3Aruby+branch%3Amain)
|
6
|
-
[](https://github.com/fixrb/matchi/actions?query=workflow%3Arubocop+branch%3Amain)
|
7
5
|
[](https://github.com/fixrb/matchi/raw/main/LICENSE.md)
|
8
6
|
|
9
|
-
|
10
|
-
Each matcher is designed to handle specific verification needs while maintaining a clear and expressive syntax.
|
7
|
+
Matchi is a lightweight, framework-agnostic Ruby library that provides a comprehensive set of expectation matchers for elegant and secure testing. Its design focuses on simplicity, security, and extensibility.
|
11
8
|
|
12
9
|

|
13
10
|
|
14
|
-
##
|
11
|
+
## Key Features
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
- **Framework Agnostic**: Easily integrate with any Ruby testing framework
|
14
|
+
- **Security-Focused Design**: Built with robust type checking for most matchers
|
15
|
+
- **Simple Integration**: Minimal setup required to get started
|
16
|
+
- **Extensible**: Create custom matchers with just a few lines of code
|
17
|
+
- **Comprehensive**: Rich set of built-in matchers for common testing scenarios
|
18
|
+
- **Well Documented**: Extensive documentation with clear examples and implementation details
|
19
|
+
- **Thread Safe**: Immutable matchers design ensures thread safety in concurrent environments
|
19
20
|
|
20
|
-
|
21
|
+
### Security Considerations for Predicate Matchers
|
21
22
|
|
22
|
-
|
23
|
+
While most Matchi matchers are designed to resist type spoofing, predicate matchers (`Matchi::Predicate`) rely on Ruby's dynamic method dispatch system and can be vulnerable to method overriding:
|
23
24
|
|
24
25
|
```ruby
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
And then execute:
|
29
|
-
|
30
|
-
```sh
|
31
|
-
bundle install
|
32
|
-
```
|
33
|
-
|
34
|
-
Or install it yourself as:
|
35
|
-
|
36
|
-
```sh
|
37
|
-
gem install matchi
|
38
|
-
```
|
39
|
-
|
40
|
-
## Overview
|
41
|
-
|
42
|
-
__Matchi__ provides a collection of damn simple expectation matchers.
|
43
|
-
|
44
|
-
## Usage
|
26
|
+
# Example of predicate matcher vulnerability:
|
27
|
+
matcher = Matchi::Predicate.new(:be_empty)
|
28
|
+
array = []
|
45
29
|
|
46
|
-
|
30
|
+
# Method overriding can defeat the matcher
|
31
|
+
def array.empty?
|
32
|
+
false
|
33
|
+
end
|
47
34
|
|
48
|
-
|
49
|
-
require "matchi"
|
35
|
+
matcher.match? { array } # => false (Even though array is empty!)
|
50
36
|
```
|
51
37
|
|
52
|
-
|
38
|
+
This limitation is inherent to Ruby's dynamic nature when working with predicate methods. If your tests require strict security guarantees, consider using direct state verification matchers instead of predicate matchers.
|
53
39
|
|
54
|
-
|
40
|
+
## What is a Matchi Matcher?
|
55
41
|
|
56
|
-
A
|
42
|
+
A Matchi matcher is a simple Ruby object that follows a specific contract:
|
57
43
|
|
58
|
-
1.
|
44
|
+
1. **Core Interface**: Every matcher must implement a `match?` method that:
|
59
45
|
- Accepts a block as its only parameter
|
60
46
|
- Executes that block to get the actual value
|
61
47
|
- Returns a boolean indicating if the actual value matches the expected criteria
|
62
48
|
|
63
|
-
2.
|
64
|
-
- `to_s`: Returns a human-readable description of the match criteria
|
65
|
-
|
66
|
-
### Using Matchers
|
49
|
+
2. **Optional Description**: Matchers can implement a `to_s` method that returns a human-readable description of the match criteria
|
67
50
|
|
68
|
-
|
69
|
-
|
70
|
-
#### 1. Direct Class Instantiation
|
71
|
-
|
72
|
-
You can create matchers directly from their classes:
|
51
|
+
Here's the simplest possible matcher:
|
73
52
|
|
74
53
|
```ruby
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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:
|
54
|
+
module Matchi
|
55
|
+
class SimpleEqual
|
56
|
+
def initialize(expected)
|
57
|
+
@expected = expected
|
58
|
+
end
|
85
59
|
|
86
|
-
|
87
|
-
|
88
|
-
class MyTestFramework
|
89
|
-
include Matchi
|
60
|
+
def match?
|
61
|
+
raise ArgumentError, "a block must be provided" unless block_given?
|
90
62
|
|
91
|
-
|
92
|
-
|
93
|
-
matcher = eq("foo")
|
94
|
-
assert(matcher.match? { "foo" })
|
63
|
+
@expected == yield
|
64
|
+
end
|
95
65
|
|
96
|
-
|
97
|
-
|
66
|
+
def to_s
|
67
|
+
"equal #{@expected.inspect}"
|
68
|
+
end
|
98
69
|
end
|
99
70
|
end
|
100
71
|
|
101
|
-
#
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
72
|
+
# Usage:
|
73
|
+
matcher = Matchi::SimpleEqual.new(42)
|
74
|
+
matcher.match? { 42 } # => true
|
75
|
+
matcher.match? { "42" } # => false
|
76
|
+
matcher.to_s # => "equal 42"
|
113
77
|
```
|
114
78
|
|
115
|
-
|
116
|
-
-
|
117
|
-
-
|
118
|
-
-
|
119
|
-
-
|
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_*`)
|
79
|
+
This design provides several benefits:
|
80
|
+
- **Lazy Evaluation**: The actual value is only computed when needed via the block
|
81
|
+
- **Encapsulation**: Each matcher is a self-contained object with clear responsibilities
|
82
|
+
- **Composability**: Matchers can be easily combined and reused
|
83
|
+
- **Testability**: The contract is simple and easy to verify
|
126
84
|
|
127
|
-
|
128
|
-
|
129
|
-
Here is the collection of generic matchers.
|
130
|
-
|
131
|
-
#### Basic Comparison Matchers
|
85
|
+
## Installation
|
132
86
|
|
133
|
-
|
134
|
-
Checks for object identity using Ruby's `equal?` method.
|
135
|
-
```ruby
|
136
|
-
Matchi::Be.new(:foo).match? { :foo } # => true (same object)
|
137
|
-
Matchi::Be.new("test").match? { "test" } # => false (different objects)
|
138
|
-
```
|
87
|
+
Add to your Gemfile:
|
139
88
|
|
140
|
-
##### `Eq`
|
141
|
-
Verifies object equivalence using Ruby's `eql?` method.
|
142
89
|
```ruby
|
143
|
-
|
144
|
-
Matchi::Eq.new([1, 2]).match? { [1, 2] } # => true (equivalent arrays)
|
90
|
+
gem "matchi"
|
145
91
|
```
|
146
92
|
|
147
|
-
|
93
|
+
Or install directly:
|
148
94
|
|
149
|
-
##### `BeAnInstanceOf`
|
150
|
-
Verifies exact class matching (no inheritance).
|
151
95
|
```ruby
|
152
|
-
|
153
|
-
Matchi::BeAnInstanceOf.new(Integer).match? { 42 } # => true
|
154
|
-
Matchi::BeAnInstanceOf.new(Numeric).match? { 42 } # => false (Integer, not Numeric)
|
155
|
-
```
|
156
|
-
|
157
|
-
##### `BeAKindOf`
|
158
|
-
Verifies class inheritance and module inclusion.
|
159
|
-
```ruby
|
160
|
-
Matchi::BeAKindOf.new(Numeric).match? { 42 } # => true (Integer inherits from Numeric)
|
161
|
-
Matchi::BeAKindOf.new(Numeric).match? { 42.0 } # => true (Float inherits from Numeric)
|
96
|
+
gem install matchi
|
162
97
|
```
|
163
98
|
|
164
|
-
|
99
|
+
## Quick Start
|
165
100
|
|
166
|
-
##### `Match`
|
167
|
-
Tests string patterns against regular expressions.
|
168
101
|
```ruby
|
169
|
-
|
170
|
-
Matchi::Match.new(/\d+/).match? { "abc123" } # => true
|
171
|
-
Matchi::Match.new(/^foo/).match? { "barfoo" } # => false
|
172
|
-
```
|
173
|
-
|
174
|
-
##### `Satisfy`
|
175
|
-
Provides custom matching through a block.
|
176
|
-
```ruby
|
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
|
179
|
-
```
|
102
|
+
require "matchi"
|
180
103
|
|
181
|
-
|
104
|
+
# Basic equality matching
|
105
|
+
Matchi::Eq.new("hello").match? { "hello" } # => true
|
182
106
|
|
183
|
-
|
184
|
-
|
107
|
+
# Type checking
|
108
|
+
Matchi::BeAKindOf.new(Numeric).match? { 42 } # => true
|
109
|
+
Matchi::BeAKindOf.new(String).match? { 42 } # => false
|
185
110
|
|
186
|
-
|
187
|
-
```ruby
|
111
|
+
# State change verification
|
188
112
|
array = []
|
189
113
|
Matchi::Change.new(array, :length).by(2).match? { array.push(1, 2) } # => true
|
190
114
|
```
|
191
115
|
|
192
|
-
|
193
|
-
```ruby
|
194
|
-
counter = 0
|
195
|
-
Matchi::Change.new(counter, :to_i).by_at_least(2).match? { counter += 3 } # => true
|
196
|
-
```
|
116
|
+
## Core Matchers
|
197
117
|
|
198
|
-
|
199
|
-
```ruby
|
200
|
-
value = 10
|
201
|
-
Matchi::Change.new(value, :to_i).by_at_most(5).match? { value += 3 } # => true
|
202
|
-
```
|
118
|
+
### Value Comparison
|
203
119
|
|
204
|
-
###### From-To Change
|
205
120
|
```ruby
|
206
|
-
|
207
|
-
Matchi::
|
121
|
+
# Exact equality (eql?)
|
122
|
+
Matchi::Eq.new("test").match? { "test" } # => true
|
123
|
+
Matchi::Eq.new([1, 2, 3]).match? { [1, 2, 3] } # => true
|
124
|
+
|
125
|
+
# Object identity (equal?)
|
126
|
+
symbol = :test
|
127
|
+
Matchi::Be.new(symbol).match? { symbol } # => true
|
128
|
+
string = "test"
|
129
|
+
Matchi::Be.new(string).match? { string.dup } # => false
|
208
130
|
```
|
209
131
|
|
210
|
-
|
132
|
+
### Type Checking
|
133
|
+
|
211
134
|
```ruby
|
212
|
-
|
213
|
-
Matchi::
|
214
|
-
|
135
|
+
# Inheritance-aware type checking
|
136
|
+
Matchi::BeAKindOf.new(Numeric).match? { 42.0 } # => true
|
137
|
+
Matchi::BeAKindOf.new(Integer).match? { 42.0 } # => false
|
215
138
|
|
216
|
-
|
139
|
+
# Exact type matching
|
140
|
+
Matchi::BeAnInstanceOf.new(Float).match? { 42.0 } # => true
|
141
|
+
Matchi::BeAnInstanceOf.new(Numeric).match? { 42.0 } # => false
|
217
142
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
Matchi::BeWithin.new(0.5).of(3.0).match? { 3.2 } # => true
|
222
|
-
Matchi::BeWithin.new(5).of(100).match? { 98 } # => true
|
143
|
+
# Using class names as strings
|
144
|
+
Matchi::BeAKindOf.new("Numeric").match? { 42.0 } # => true
|
145
|
+
Matchi::BeAnInstanceOf.new("Float").match? { 42.0 } # => true
|
223
146
|
```
|
224
147
|
|
225
|
-
|
148
|
+
### State Changes
|
226
149
|
|
227
|
-
##### `RaiseException`
|
228
|
-
Verifies that code raises specific exceptions.
|
229
150
|
```ruby
|
230
|
-
|
231
|
-
|
232
|
-
|
151
|
+
# Verify exact changes
|
152
|
+
counter = 0
|
153
|
+
Matchi::Change.new(counter, :to_i).by(5).match? { counter += 5 } # => true
|
233
154
|
|
234
|
-
|
235
|
-
|
155
|
+
# Verify minimum changes
|
156
|
+
Matchi::Change.new(counter, :to_i).by_at_least(2).match? { counter += 3 } # => true
|
236
157
|
|
237
|
-
|
238
|
-
|
239
|
-
Matchi::Predicate.new(:be_empty).match? { [] } # => true (calls empty?)
|
240
|
-
Matchi::Predicate.new(:be_nil).match? { nil } # => true (calls nil?)
|
241
|
-
```
|
158
|
+
# Verify maximum changes
|
159
|
+
Matchi::Change.new(counter, :to_i).by_at_most(5).match? { counter += 3 } # => true
|
242
160
|
|
243
|
-
|
244
|
-
|
245
|
-
Matchi::
|
246
|
-
```
|
161
|
+
# Track value transitions
|
162
|
+
string = "hello"
|
163
|
+
Matchi::Change.new(string, :to_s).from("hello").to("HELLO").match? { string.upcase! } # => true
|
247
164
|
|
248
|
-
|
165
|
+
# Simple change detection
|
166
|
+
array = []
|
167
|
+
Matchi::Change.new(array, :length).match? { array << 1 } # => true
|
249
168
|
|
250
|
-
|
169
|
+
# Check final state only
|
170
|
+
counter = 0
|
171
|
+
Matchi::Change.new(counter, :to_i).to(5).match? { counter = 5 } # => true
|
172
|
+
```
|
251
173
|
|
252
|
-
|
174
|
+
### Pattern Matching
|
253
175
|
|
254
176
|
```ruby
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
expected.equal?(yield)
|
259
|
-
end
|
177
|
+
# Regular expressions
|
178
|
+
Matchi::Match.new(/^test/).match? { "test_string" } # => true
|
179
|
+
Matchi::Match.new(/^\d{3}-\d{2}$/).match? { "123-45" } # => true
|
260
180
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
42
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
181
|
+
# Custom predicates with Satisfy
|
182
|
+
Matchi::Satisfy.new { |x| x.positive? && x < 10 }.match? { 5 } # => true
|
183
|
+
Matchi::Satisfy.new { |arr| arr.all?(&:even?) }.match? { [2, 4, 6] } # => true
|
268
184
|
|
269
|
-
|
270
|
-
|
185
|
+
# Built-in predicates
|
186
|
+
Matchi::Predicate.new(:be_empty).match? { [] } # => true
|
187
|
+
Matchi::Predicate.new(:have_key, :name).match? { { name: "Alice" } } # => true
|
271
188
|
```
|
272
189
|
|
273
|
-
|
190
|
+
### Exception Handling
|
274
191
|
|
275
192
|
```ruby
|
276
|
-
|
193
|
+
# Verify raised exceptions
|
194
|
+
Matchi::RaiseException.new(ArgumentError).match? { raise ArgumentError } # => true
|
277
195
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
end
|
196
|
+
# Works with inheritance
|
197
|
+
Matchi::RaiseException.new(StandardError).match? { raise ArgumentError } # => true
|
198
|
+
|
199
|
+
# Using exception class names
|
200
|
+
Matchi::RaiseException.new("ArgumentError").match? { raise ArgumentError } # => true
|
201
|
+
```
|
285
202
|
|
286
|
-
|
203
|
+
### Numeric Comparisons
|
287
204
|
|
288
|
-
|
205
|
+
```ruby
|
206
|
+
# Delta comparisons
|
207
|
+
Matchi::BeWithin.new(0.5).of(3.0).match? { 3.2 } # => true
|
208
|
+
Matchi::BeWithin.new(2).of(10).match? { 9 } # => true
|
289
209
|
```
|
290
210
|
|
291
|
-
|
211
|
+
## Creating Custom Matchers
|
212
|
+
|
213
|
+
Creating custom matchers is straightforward:
|
292
214
|
|
293
215
|
```ruby
|
294
216
|
module Matchi
|
295
|
-
class
|
296
|
-
def
|
297
|
-
|
217
|
+
class BePositive
|
218
|
+
def match?
|
219
|
+
yield.positive?
|
298
220
|
end
|
299
221
|
|
300
|
-
def
|
301
|
-
|
222
|
+
def to_s
|
223
|
+
"be positive"
|
302
224
|
end
|
303
225
|
end
|
304
226
|
end
|
305
227
|
|
306
|
-
matcher = Matchi::
|
307
|
-
matcher.match? {
|
228
|
+
matcher = Matchi::BePositive.new
|
229
|
+
matcher.match? { 42 } # => true
|
230
|
+
matcher.match? { -1 } # => false
|
308
231
|
```
|
309
232
|
|
310
|
-
## Best Practices
|
233
|
+
## Security Best Practices
|
311
234
|
|
312
235
|
### Proper Value Comparison Order
|
313
236
|
|
@@ -316,7 +239,6 @@ One of the most critical aspects when implementing matchers is the order of comp
|
|
316
239
|
```ruby
|
317
240
|
# GOOD: Expected value controls the comparison
|
318
241
|
expected_value.eql?(actual_value)
|
319
|
-
|
320
242
|
# BAD: Actual value controls the comparison
|
321
243
|
actual_value.eql?(expected_value)
|
322
244
|
```
|
@@ -339,7 +261,6 @@ end
|
|
339
261
|
|
340
262
|
actual = MaliciousString.new
|
341
263
|
expected = "expected string"
|
342
|
-
|
343
264
|
actual.eql?(expected) # => true (incorrect result!)
|
344
265
|
expected.eql?(actual) # => false (correct result)
|
345
266
|
```
|
@@ -353,14 +274,22 @@ def match?
|
|
353
274
|
end
|
354
275
|
```
|
355
276
|
|
356
|
-
##
|
277
|
+
## Extensions
|
278
|
+
|
279
|
+
### matchi-fix
|
280
|
+
|
281
|
+
The [matchi-fix gem](https://rubygems.org/gems/matchi-fix) extends Matchi with support for testing against [Fix](https://github.com/fixrb/fix) specifications. It provides a seamless integration between Matchi's matcher interface and Fix's powerful specification system.
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
# Add to your Gemfile
|
285
|
+
gem "matchi-fix"
|
286
|
+
```
|
357
287
|
|
358
|
-
|
359
|
-
* Bugs/issues: https://github.com/fixrb/matchi/issues
|
288
|
+
This extension adds a `Fix` matcher that allows you to verify implementation conformance to Fix test specifications across different testing frameworks like Minitest and RSpec.
|
360
289
|
|
361
290
|
## Versioning
|
362
291
|
|
363
|
-
|
292
|
+
Matchi follows [Semantic Versioning 2.0](https://semver.org/).
|
364
293
|
|
365
294
|
## License
|
366
295
|
|
data/lib/matchi/be.rb
CHANGED
@@ -1,41 +1,83 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Matchi
|
4
|
-
#
|
4
|
+
# Identity matcher that checks if two objects are the exact same instance.
|
5
|
+
#
|
6
|
+
# This matcher verifies object identity using Ruby's Object#equal? method, which
|
7
|
+
# compares object IDs to determine if two references point to the exact same object
|
8
|
+
# in memory. This is different from equality comparison (==) which compares values.
|
9
|
+
#
|
10
|
+
# @example Basic usage with symbols
|
11
|
+
# matcher = Matchi::Be.new(:foo)
|
12
|
+
# matcher.match? { :foo } # => true
|
13
|
+
# matcher.match? { :bar } # => false
|
14
|
+
#
|
15
|
+
# @example Identity comparison with strings
|
16
|
+
# string = "test"
|
17
|
+
# matcher = Matchi::Be.new(string)
|
18
|
+
# matcher.match? { string } # => true
|
19
|
+
# matcher.match? { string.dup } # => false
|
20
|
+
# matcher.match? { "test" } # => false
|
21
|
+
#
|
22
|
+
# @example With mutable objects
|
23
|
+
# array = [1, 2, 3]
|
24
|
+
# matcher = Matchi::Be.new(array)
|
25
|
+
# matcher.match? { array } # => true
|
26
|
+
# matcher.match? { array.dup } # => false
|
27
|
+
# matcher.match? { [1, 2, 3] } # => false
|
28
|
+
#
|
29
|
+
# @see https://ruby-doc.org/core/Object.html#method-i-equal-3F
|
30
|
+
# @see Matchi::Eq
|
5
31
|
class Be
|
6
|
-
# Initialize the matcher with
|
32
|
+
# Initialize the matcher with a reference object.
|
7
33
|
#
|
8
|
-
# @
|
9
|
-
#
|
34
|
+
# @api public
|
35
|
+
#
|
36
|
+
# @param expected [#equal?] The expected identical object
|
10
37
|
#
|
11
|
-
#
|
38
|
+
# @return [Be] a new instance of the matcher
|
12
39
|
#
|
13
|
-
# @
|
40
|
+
# @example
|
41
|
+
# string = "test"
|
42
|
+
# Be.new(string) # Match only the same string instance
|
14
43
|
def initialize(expected)
|
15
44
|
@expected = expected
|
16
45
|
end
|
17
46
|
|
18
|
-
#
|
47
|
+
# Checks if the yielded object is the same instance as the expected object.
|
19
48
|
#
|
20
|
-
#
|
21
|
-
#
|
49
|
+
# This method uses Ruby's Object#equal? method, which performs identity comparison
|
50
|
+
# by comparing object IDs. Two objects are considered identical only if they are
|
51
|
+
# the exact same instance in memory.
|
52
|
+
#
|
53
|
+
# @api public
|
22
54
|
#
|
23
|
-
#
|
24
|
-
#
|
55
|
+
# @yield [] Block that returns the object to check
|
56
|
+
# @yieldreturn [Object] The object to verify identity with
|
25
57
|
#
|
26
|
-
# @
|
27
|
-
# one.
|
58
|
+
# @return [Boolean] true if both objects are the same instance
|
28
59
|
#
|
29
|
-
# @
|
60
|
+
# @raise [ArgumentError] if no block is provided
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# obj = "test"
|
64
|
+
# matcher = Be.new(obj)
|
65
|
+
# matcher.match? { obj } # => true
|
66
|
+
# matcher.match? { obj.dup } # => false
|
30
67
|
def match?
|
31
68
|
raise ::ArgumentError, "a block must be provided" unless block_given?
|
32
69
|
|
33
70
|
@expected.equal?(yield)
|
34
71
|
end
|
35
72
|
|
36
|
-
# Returns a
|
73
|
+
# Returns a human-readable description of the matcher.
|
74
|
+
#
|
75
|
+
# @api public
|
37
76
|
#
|
38
|
-
# @return [String]
|
77
|
+
# @return [String] A string describing what this matcher verifies
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# Be.new("test").to_s # => 'be "test"'
|
39
81
|
def to_s
|
40
82
|
"be #{@expected.inspect}"
|
41
83
|
end
|