matchi 3.3.2 → 4.1.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 +201 -90
- data/lib/matchi/be.rb +7 -13
- data/lib/matchi/be_a_kind_of.rb +81 -0
- data/lib/matchi/be_an_instance_of.rb +91 -21
- data/lib/matchi/be_within/of.rb +11 -13
- data/lib/matchi/be_within.rb +4 -1
- data/lib/matchi/change/by.rb +11 -14
- data/lib/matchi/change/by_at_least.rb +12 -14
- data/lib/matchi/change/by_at_most.rb +12 -14
- data/lib/matchi/change/from/to.rb +10 -14
- data/lib/matchi/change/from.rb +3 -1
- data/lib/matchi/change/to.rb +10 -14
- data/lib/matchi/change.rb +8 -7
- data/lib/matchi/eq.rb +7 -13
- data/lib/matchi/match.rb +9 -13
- data/lib/matchi/predicate.rb +8 -19
- data/lib/matchi/raise_exception.rb +30 -17
- data/lib/matchi/satisfy.rb +8 -12
- data/lib/matchi.rb +300 -0
- metadata +4 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 730c0baf16a64842af123077150d70aee64e664ef6d5626dadf4730c1ee3425d
|
4
|
+
data.tar.gz: a934654b282119e28d4fb5c4079298770f86a834d58621240737f2473f067500
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75884ba2533b155d3f1e4490f9b8f6071a95aad0fca637dcdb0eee2bf60e1a3dc6bd7cab35ab93a3a7c18b680b9d789ee71bfbd7d524e6ca5ea8573520a3deec
|
7
|
+
data.tar.gz: d421e42619fc45d1f1aab95ccde2a24d6a1226f4163a4afbde02fc8e693343997fa5a9a0081d72734955b2b6a25b3f9f735766702860d2184c44f403e169f442
|
data/README.md
CHANGED
@@ -6,7 +6,8 @@
|
|
6
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
7
|
[![License](https://img.shields.io/github/license/fixrb/matchi?label=License&logo=github)](https://github.com/fixrb/matchi/raw/main/LICENSE.md)
|
8
8
|
|
9
|
-
|
9
|
+
This library provides a comprehensive set of matchers for testing different aspects of your code.
|
10
|
+
Each matcher is designed to handle specific verification needs while maintaining a clear and expressive syntax.
|
10
11
|
|
11
12
|
![A Rubyist juggling between Matchi letters](https://github.com/fixrb/matchi/raw/main/img/matchi.png)
|
12
13
|
|
@@ -52,148 +53,221 @@ All examples here assume that this has been done.
|
|
52
53
|
|
53
54
|
### Anatomy of a matcher
|
54
55
|
|
55
|
-
A
|
56
|
+
A **Matchi** matcher is a simple Ruby object that follows these requirements:
|
56
57
|
|
57
|
-
|
58
|
+
1. It must implement a `match?` method that:
|
59
|
+
- Accepts a block as its only parameter
|
60
|
+
- Executes that block to get the actual value
|
61
|
+
- Returns a boolean indicating if the actual value matches the expected criteria
|
58
62
|
|
59
|
-
|
63
|
+
2. Optionally, it may implement:
|
64
|
+
- `to_s`: Returns a human-readable description of the match criteria
|
65
|
+
|
66
|
+
### Using Matchers
|
67
|
+
|
68
|
+
There are two main ways to use Matchi matchers:
|
60
69
|
|
61
|
-
|
70
|
+
#### 1. Direct Class Instantiation
|
62
71
|
|
63
|
-
|
72
|
+
You can create matchers directly from their classes:
|
64
73
|
|
65
74
|
```ruby
|
66
75
|
matcher = Matchi::Eq.new("foo")
|
76
|
+
matcher.match? { "foo" } # => true
|
67
77
|
|
68
|
-
matcher
|
69
|
-
matcher.
|
78
|
+
matcher = Matchi::BeWithin.new(0.5).of(3.0)
|
79
|
+
matcher.match? { 3.2 } # => true
|
70
80
|
```
|
71
81
|
|
72
|
-
|
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:
|
73
85
|
|
74
86
|
```ruby
|
75
|
-
|
87
|
+
# Including in a class for instance methods
|
88
|
+
class MyTestFramework
|
89
|
+
include Matchi
|
76
90
|
|
77
|
-
|
78
|
-
|
79
|
-
|
91
|
+
def test_equality
|
92
|
+
# Helper methods available as instance methods
|
93
|
+
matcher = eq("foo")
|
94
|
+
assert(matcher.match? { "foo" })
|
80
95
|
|
81
|
-
|
96
|
+
# Method chaining works too
|
97
|
+
assert(change(@array, :length).by(1).match? { @array << 1 })
|
98
|
+
end
|
99
|
+
end
|
82
100
|
|
83
|
-
|
84
|
-
|
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
|
85
108
|
|
86
|
-
|
87
|
-
|
109
|
+
def self.assert_within_range(expected, delta, actual)
|
110
|
+
be_within(delta).of(expected).match? { actual }
|
111
|
+
end
|
112
|
+
end
|
88
113
|
```
|
89
114
|
|
90
|
-
|
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_*`)
|
91
126
|
|
92
|
-
|
93
|
-
matcher = Matchi::Match.new(/^foo$/)
|
127
|
+
### Built-in matchers
|
94
128
|
|
95
|
-
|
96
|
-
matcher.matches? { "foo" } # => true
|
97
|
-
```
|
129
|
+
Here is the collection of generic matchers.
|
98
130
|
|
99
|
-
|
131
|
+
#### Basic Comparison Matchers
|
100
132
|
|
133
|
+
##### `Be`
|
134
|
+
Checks for object identity using Ruby's `equal?` method.
|
101
135
|
```ruby
|
102
|
-
|
136
|
+
Matchi::Be.new(:foo).match? { :foo } # => true (same object)
|
137
|
+
Matchi::Be.new("test").match? { "test" } # => false (different objects)
|
138
|
+
```
|
103
139
|
|
104
|
-
|
105
|
-
|
140
|
+
##### `Eq`
|
141
|
+
Verifies object equivalence using Ruby's `eql?` method.
|
142
|
+
```ruby
|
143
|
+
Matchi::Eq.new("foo").match? { "foo" } # => true (equivalent content)
|
144
|
+
Matchi::Eq.new([1, 2]).match? { [1, 2] } # => true (equivalent arrays)
|
106
145
|
```
|
107
146
|
|
108
|
-
|
147
|
+
#### Type and Class Matchers
|
109
148
|
|
149
|
+
##### `BeAnInstanceOf`
|
150
|
+
Verifies exact class matching (no inheritance).
|
110
151
|
```ruby
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
matcher.matches? { "foo" } # => true
|
152
|
+
Matchi::BeAnInstanceOf.new(String).match? { "test" } # => true
|
153
|
+
Matchi::BeAnInstanceOf.new(Integer).match? { 42 } # => true
|
154
|
+
Matchi::BeAnInstanceOf.new(Numeric).match? { 42 } # => false (Integer, not Numeric)
|
115
155
|
```
|
116
156
|
|
117
|
-
|
118
|
-
|
157
|
+
##### `BeAKindOf`
|
158
|
+
Verifies class inheritance and module inclusion.
|
119
159
|
```ruby
|
120
|
-
|
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)
|
162
|
+
```
|
121
163
|
|
122
|
-
|
123
|
-
matcher.matches? { [] } # => true
|
164
|
+
#### Pattern Matchers
|
124
165
|
|
125
|
-
|
166
|
+
##### `Match`
|
167
|
+
Tests string patterns against regular expressions.
|
168
|
+
```ruby
|
169
|
+
Matchi::Match.new(/^foo/).match? { "foobar" } # => true
|
170
|
+
Matchi::Match.new(/\d+/).match? { "abc123" } # => true
|
171
|
+
Matchi::Match.new(/^foo/).match? { "barfoo" } # => false
|
172
|
+
```
|
126
173
|
|
127
|
-
|
128
|
-
|
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
|
129
179
|
```
|
130
180
|
|
131
|
-
|
181
|
+
#### State Change Matchers
|
132
182
|
|
133
|
-
|
134
|
-
|
135
|
-
matcher = Matchi::Change.new(object, :length).by(1)
|
183
|
+
##### `Change`
|
184
|
+
Verifies state changes in objects with multiple variation methods:
|
136
185
|
|
137
|
-
|
138
|
-
|
186
|
+
###### Basic Change
|
187
|
+
```ruby
|
188
|
+
array = []
|
189
|
+
Matchi::Change.new(array, :length).by(2).match? { array.push(1, 2) } # => true
|
190
|
+
```
|
139
191
|
|
140
|
-
|
141
|
-
|
192
|
+
###### Minimum Change
|
193
|
+
```ruby
|
194
|
+
counter = 0
|
195
|
+
Matchi::Change.new(counter, :to_i).by_at_least(2).match? { counter += 3 } # => true
|
196
|
+
```
|
142
197
|
|
143
|
-
|
144
|
-
|
198
|
+
###### Maximum Change
|
199
|
+
```ruby
|
200
|
+
value = 10
|
201
|
+
Matchi::Change.new(value, :to_i).by_at_most(5).match? { value += 3 } # => true
|
202
|
+
```
|
145
203
|
|
146
|
-
|
147
|
-
|
204
|
+
###### From-To Change
|
205
|
+
```ruby
|
206
|
+
string = "hello"
|
207
|
+
Matchi::Change.new(string, :upcase).from("hello").to("HELLO").match? { string.upcase! } # => true
|
208
|
+
```
|
148
209
|
|
149
|
-
|
150
|
-
|
210
|
+
###### To-Only Change
|
211
|
+
```ruby
|
212
|
+
number = 1
|
213
|
+
Matchi::Change.new(number, :to_i).to(5).match? { number = 5 } # => true
|
214
|
+
```
|
151
215
|
|
152
|
-
|
153
|
-
matcher = Matchi::Change.new(object, :to_s).from("foo").to("FOO")
|
216
|
+
#### Numeric Matchers
|
154
217
|
|
155
|
-
|
156
|
-
|
218
|
+
##### `BeWithin`
|
219
|
+
Checks if a number is within a specified range of an expected value.
|
220
|
+
```ruby
|
221
|
+
Matchi::BeWithin.new(0.5).of(3.0).match? { 3.2 } # => true
|
222
|
+
Matchi::BeWithin.new(5).of(100).match? { 98 } # => true
|
223
|
+
```
|
157
224
|
|
158
|
-
|
159
|
-
matcher = Matchi::Change.new(object, :to_s).to("FOO")
|
225
|
+
#### Behavior Matchers
|
160
226
|
|
161
|
-
|
162
|
-
|
227
|
+
##### `RaiseException`
|
228
|
+
Verifies that code raises specific exceptions.
|
229
|
+
```ruby
|
230
|
+
Matchi::RaiseException.new(ArgumentError).match? { raise ArgumentError } # => true
|
231
|
+
Matchi::RaiseException.new(NameError).match? { undefined_variable } # => true
|
163
232
|
```
|
164
233
|
|
165
|
-
|
234
|
+
##### `Predicate`
|
235
|
+
Creates matchers for methods ending in `?`.
|
166
236
|
|
237
|
+
###### Using `be_` prefix
|
167
238
|
```ruby
|
168
|
-
|
239
|
+
Matchi::Predicate.new(:be_empty).match? { [] } # => true (calls empty?)
|
240
|
+
Matchi::Predicate.new(:be_nil).match? { nil } # => true (calls nil?)
|
241
|
+
```
|
169
242
|
|
170
|
-
|
171
|
-
|
243
|
+
###### Using `have_` prefix
|
244
|
+
```ruby
|
245
|
+
Matchi::Predicate.new(:have_key, :foo).match? { { foo: 42 } } # => true (calls has_key?)
|
172
246
|
```
|
173
247
|
|
174
248
|
### Custom matchers
|
175
249
|
|
176
|
-
Custom matchers
|
250
|
+
Custom matchers could easily be added to `Matchi` module to express more specific expectations.
|
177
251
|
|
178
252
|
A **Be the answer** matcher:
|
179
253
|
|
180
254
|
```ruby
|
181
255
|
module Matchi
|
182
256
|
class BeTheAnswer
|
183
|
-
def
|
184
|
-
|
257
|
+
def match?
|
258
|
+
expected.equal?(yield)
|
185
259
|
end
|
186
260
|
|
187
|
-
|
188
|
-
|
261
|
+
private
|
262
|
+
|
263
|
+
def expected
|
264
|
+
42
|
189
265
|
end
|
190
266
|
end
|
191
267
|
end
|
192
268
|
|
193
269
|
matcher = Matchi::BeTheAnswer.new
|
194
|
-
|
195
|
-
matcher.expected # => 42
|
196
|
-
matcher.matches? { 42 } # => true
|
270
|
+
matcher.match? { 42 } # => true
|
197
271
|
```
|
198
272
|
|
199
273
|
A **Be prime** matcher:
|
@@ -203,7 +277,7 @@ require "prime"
|
|
203
277
|
|
204
278
|
module Matchi
|
205
279
|
class BePrime
|
206
|
-
def
|
280
|
+
def match?
|
207
281
|
Prime.prime?(yield)
|
208
282
|
end
|
209
283
|
end
|
@@ -211,7 +285,7 @@ end
|
|
211
285
|
|
212
286
|
matcher = Matchi::BePrime.new
|
213
287
|
|
214
|
-
matcher.
|
288
|
+
matcher.match? { 42 } # => false
|
215
289
|
```
|
216
290
|
|
217
291
|
A **Start with** matcher:
|
@@ -219,22 +293,64 @@ A **Start with** matcher:
|
|
219
293
|
```ruby
|
220
294
|
module Matchi
|
221
295
|
class StartWith
|
222
|
-
attr_reader :expected
|
223
|
-
|
224
296
|
def initialize(expected)
|
225
297
|
@expected = expected
|
226
298
|
end
|
227
299
|
|
228
|
-
def
|
229
|
-
/\A#{expected}/.match?(yield)
|
300
|
+
def match?
|
301
|
+
/\A#{@expected}/.match?(yield)
|
230
302
|
end
|
231
303
|
end
|
232
304
|
end
|
233
305
|
|
234
306
|
matcher = Matchi::StartWith.new("foo")
|
307
|
+
matcher.match? { "foobar" } # => true
|
308
|
+
```
|
235
309
|
|
236
|
-
|
237
|
-
|
310
|
+
## Best Practices
|
311
|
+
|
312
|
+
### Proper Value Comparison Order
|
313
|
+
|
314
|
+
One of the most critical aspects when implementing matchers is the order of comparison between expected and actual values. Always compare values in this order:
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
# GOOD: Expected value controls the comparison
|
318
|
+
expected_value.eql?(actual_value)
|
319
|
+
|
320
|
+
# BAD: Actual value controls the comparison
|
321
|
+
actual_value.eql?(expected_value)
|
322
|
+
```
|
323
|
+
|
324
|
+
#### Why This Matters
|
325
|
+
|
326
|
+
The order is crucial because the object receiving the comparison method controls how the comparison is performed. When testing, the actual value might come from untrusted or malicious code that could override comparison methods:
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
# Example of how comparison can be compromised
|
330
|
+
class MaliciousString
|
331
|
+
def eql?(other)
|
332
|
+
true # Always returns true regardless of actual equality
|
333
|
+
end
|
334
|
+
|
335
|
+
def ==(other)
|
336
|
+
true # Always returns true regardless of actual equality
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
actual = MaliciousString.new
|
341
|
+
expected = "expected string"
|
342
|
+
|
343
|
+
actual.eql?(expected) # => true (incorrect result!)
|
344
|
+
expected.eql?(actual) # => false (correct result)
|
345
|
+
```
|
346
|
+
|
347
|
+
This is why Matchi's built-in matchers are implemented with this security consideration in mind. For example, the `Eq` matcher:
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
# Implementation in Matchi::Eq
|
351
|
+
def match?
|
352
|
+
@expected.eql?(yield) # Expected value controls the comparison
|
353
|
+
end
|
238
354
|
```
|
239
355
|
|
240
356
|
## Contact
|
@@ -250,11 +366,6 @@ __Matchi__ follows [Semantic Versioning 2.0](https://semver.org/).
|
|
250
366
|
|
251
367
|
The [gem](https://rubygems.org/gems/matchi) is available as open source under the terms of the [MIT License](https://github.com/fixrb/matchi/raw/main/LICENSE.md).
|
252
368
|
|
253
|
-
|
369
|
+
## Sponsors
|
254
370
|
|
255
|
-
|
256
|
-
This project is sponsored by:<br />
|
257
|
-
<a href="https://sashite.com/"><img
|
258
|
-
src="https://github.com/fixrb/matchi/raw/main/img/sashite.png"
|
259
|
-
alt="Sashité" /></a>
|
260
|
-
</p>
|
371
|
+
This project is sponsored by [Sashité](https://sashite.com/)
|
data/lib/matchi/be.rb
CHANGED
@@ -3,9 +3,6 @@
|
|
3
3
|
module Matchi
|
4
4
|
# *Identity* matcher.
|
5
5
|
class Be
|
6
|
-
# @return [#equal?] The expected identical object.
|
7
|
-
attr_reader :expected
|
8
|
-
|
9
6
|
# Initialize the matcher with an object.
|
10
7
|
#
|
11
8
|
# @example
|
@@ -24,26 +21,23 @@ module Matchi
|
|
24
21
|
# require "matchi/be"
|
25
22
|
#
|
26
23
|
# matcher = Matchi::Be.new(:foo)
|
27
|
-
#
|
28
|
-
# matcher.expected # => :foo
|
29
|
-
# matcher.matches? { :foo } # => true
|
24
|
+
# matcher.match? { :foo } # => true
|
30
25
|
#
|
31
26
|
# @yieldreturn [#object_id] The actual value to compare to the expected
|
32
27
|
# one.
|
33
28
|
#
|
34
29
|
# @return [Boolean] Comparison between actual and expected values.
|
35
|
-
def
|
36
|
-
|
37
|
-
end
|
30
|
+
def match?
|
31
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
38
32
|
|
39
|
-
|
40
|
-
def inspect
|
41
|
-
"#{self.class}(#{expected.inspect})"
|
33
|
+
@expected.equal?(yield)
|
42
34
|
end
|
43
35
|
|
44
36
|
# Returns a string representing the matcher.
|
37
|
+
#
|
38
|
+
# @return [String] a human-readable description of the matcher
|
45
39
|
def to_s
|
46
|
-
"be #{expected.inspect}"
|
40
|
+
"be #{@expected.inspect}"
|
47
41
|
end
|
48
42
|
end
|
49
43
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Matchi
|
4
|
+
# *Type/class* matcher for inheritance-aware type checking.
|
5
|
+
#
|
6
|
+
# This matcher provides a clear way to check if an object is an instance of a
|
7
|
+
# specific class or one of its subclasses. It leverages Ruby's native === operator
|
8
|
+
# which reliably handles class hierarchy relationships.
|
9
|
+
#
|
10
|
+
# @example Basic usage
|
11
|
+
# require "matchi/be_a_kind_of"
|
12
|
+
#
|
13
|
+
# matcher = Matchi::BeAKindOf.new(Numeric)
|
14
|
+
# matcher.match? { 42 } # => true
|
15
|
+
# matcher.match? { 42.0 } # => true
|
16
|
+
# matcher.match? { "42" } # => false
|
17
|
+
class BeAKindOf
|
18
|
+
# Initialize the matcher with (the name of) a class or module.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# require "matchi/be_a_kind_of"
|
22
|
+
#
|
23
|
+
# Matchi::BeAKindOf.new(String)
|
24
|
+
# Matchi::BeAKindOf.new("String")
|
25
|
+
# Matchi::BeAKindOf.new(:String)
|
26
|
+
#
|
27
|
+
# @param expected [Class, #to_s] The expected class name
|
28
|
+
# @raise [ArgumentError] if the class name doesn't start with an uppercase letter
|
29
|
+
def initialize(expected)
|
30
|
+
@expected = String(expected)
|
31
|
+
return if /\A[A-Z]/.match?(@expected)
|
32
|
+
|
33
|
+
raise ::ArgumentError,
|
34
|
+
"expected must start with an uppercase letter (got: #{@expected})"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks if the yielded object is an instance of the expected class
|
38
|
+
# or one of its subclasses.
|
39
|
+
#
|
40
|
+
# This method uses the case equality operator (===) which provides a reliable
|
41
|
+
# way to check class hierarchy relationships in Ruby. When a class is the
|
42
|
+
# receiver of ===, it returns true if the argument is an instance of that
|
43
|
+
# class or one of its subclasses.
|
44
|
+
#
|
45
|
+
# @example Class hierarchy check
|
46
|
+
# class Animal; end
|
47
|
+
# class Dog < Animal; end
|
48
|
+
#
|
49
|
+
# matcher = Matchi::BeAKindOf.new(Animal)
|
50
|
+
# matcher.match? { Dog.new } # => true
|
51
|
+
# matcher.match? { Animal.new } # => true
|
52
|
+
# matcher.match? { Object.new } # => false
|
53
|
+
#
|
54
|
+
# @yieldreturn [Object] the actual value to check
|
55
|
+
# @return [Boolean] true if the object is an instance of the expected class or one of its subclasses
|
56
|
+
# @raise [ArgumentError] if no block is provided
|
57
|
+
def match?
|
58
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
59
|
+
|
60
|
+
expected_class === yield # rubocop:disable Style/CaseEquality
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns a string representing the matcher.
|
64
|
+
#
|
65
|
+
# @return [String] a human-readable description of the matcher
|
66
|
+
def to_s
|
67
|
+
"be a kind of #{@expected}"
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Resolves the expected class name to an actual Class object.
|
73
|
+
# This method handles both string and symbol class names through constant resolution.
|
74
|
+
#
|
75
|
+
# @return [Class] the resolved class
|
76
|
+
# @raise [NameError] if the class doesn't exist
|
77
|
+
def expected_class
|
78
|
+
::Object.const_get(@expected)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|