matchi 4.1.0 → 4.2.0
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 +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
|
[![Version](https://img.shields.io/github/v/tag/fixrb/matchi?label=Version&logo=github)](https://github.com/fixrb/matchi/tags)
|
4
4
|
[![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/matchi/main)
|
5
|
-
[![Ruby](https://github.com/fixrb/matchi/workflows/Ruby/badge.svg?branch=main)](https://github.com/fixrb/matchi/actions?query=workflow%3Aruby+branch%3Amain)
|
6
|
-
[![RuboCop](https://github.com/fixrb/matchi/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/matchi/actions?query=workflow%3Arubocop+branch%3Amain)
|
7
5
|
[![License](https://img.shields.io/github/license/fixrb/matchi?label=License&logo=github)](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
|
![A Rubyist juggling between Matchi letters](https://github.com/fixrb/matchi/raw/main/img/matchi.png)
|
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
|