matchi 2.3.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96a52462a574281d4d485558a112fec5cef2e00f1fb90c13987407c18302887c
4
- data.tar.gz: 00511b2878ebc9bbe2deb07f888944c9b2e2f2cebf9d8a78d1fe40e1a2b923c7
3
+ metadata.gz: 8da45849405d96c572ada45280a0acb673276092d25bc56c6a423892f1e1c1b8
4
+ data.tar.gz: 61f642b16e141704436eb2295fd0c5bc3bda93bb3991c992d40b71f491a328f4
5
5
  SHA512:
6
- metadata.gz: d368cf94df8ebbfe50b7d5ffd521653853096cd7c36c1c23093916899d0f2524ccf3f3526495cc37e043af571b0df68652c406e7effb033c8d2e92c661dd2b4a
7
- data.tar.gz: 5646d8c021cc964041af44412aefab04cc27ebbaa51ddf381b81b43577f233be8f4621735000f1ef7b63e759d80cf51e67c8c802ace3634e2174f72ee8b4ab9f
6
+ metadata.gz: 8ddb08586b88d7bc3a1447b1ba503aa3170e587ba7a82134d660706be1e5a46ecf5f6180e16feb4736ebd2558e7bfc13f8474bdcca8f0a676945daa04d4ce04f
7
+ data.tar.gz: f0433134ba8bc1d93e4d4dbc6c8223c3033eda63525defb8a385c4cefc4bc43618b89cb97a47668060b744cfdac57b39e19dbdfebc7867a43ff18f7065d373c1
data/README.md CHANGED
@@ -6,9 +6,14 @@
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
- > Collection of expectation matchers for Ruby 🤹
9
+ > Collection of expectation matchers for Rubyists 🤹
10
10
 
11
- ![A rubyist juggling between colored balls representing expectation matchers](https://github.com/fixrb/matchi/raw/main/img/matchi.jpg)
11
+ ![A Rubyist juggling between Matchi letters](https://github.com/fixrb/matchi/raw/main/img/matchi.jpg)
12
+
13
+ ## Project goals
14
+
15
+ * Adding matchers should be as simple as possible.
16
+ * Being framework agnostic and easy to integrate.
12
17
 
13
18
  ## Installation
14
19
 
@@ -44,91 +49,131 @@ require "matchi"
44
49
 
45
50
  All examples here assume that this has been done.
46
51
 
52
+ ### Anatomy of a matcher
53
+
54
+ A __Matchi__ matcher is an object that must respond to the `matches?` method with a block as argument, and return a boolean.
55
+
56
+ To facilitate the integration of the matchers in other tools, it is recommended to expose the expected value via the `expected` method.
57
+
58
+ That's all it is.
59
+
60
+ Let's see some examples.
61
+
47
62
  ### Built-in matchers
48
63
 
64
+ Here is the collection of useful generic matchers.
65
+
49
66
  **Equivalence** matcher:
50
67
 
51
68
  ```ruby
52
- eql = Matchi::Matcher::Eql.new("foo")
53
- eql.matches? { "foo" } # => true
69
+ matcher = Matchi::Eq.new("foo")
70
+
71
+ matcher.expected # => "foo"
72
+ matcher.matches? { "foo" } # => true
54
73
  ```
55
74
 
56
75
  **Identity** matcher:
57
76
 
58
77
  ```ruby
59
- equal = Matchi::Matcher::Equal.new(:foo)
60
- equal.matches? { :foo } # => true
78
+ matcher = Matchi::Be.new(:foo)
79
+
80
+ matcher.expected # => :foo
81
+ matcher.matches? { :foo } # => true
61
82
  ```
62
83
 
63
84
  **Regular expressions** matcher:
64
85
 
65
86
  ```ruby
66
- match = Matchi::Matcher::Match.new(/^foo$/)
67
- match.matches? { "foo" } # => true
87
+ matcher = Matchi::Match.new(/^foo$/)
88
+
89
+ matcher.expected # => /^foo$/
90
+ matcher.matches? { "foo" } # => true
68
91
  ```
69
92
 
70
93
  **Expecting errors** matcher:
71
94
 
72
95
  ```ruby
73
- raise_exception = Matchi::Matcher::RaiseException.new(NameError)
74
- raise_exception.matches? { Boom } # => true
96
+ matcher = Matchi::RaiseException.new(:NameError)
97
+
98
+ matcher.expected # => :NameError
99
+ matcher.matches? { Boom } # => true
75
100
  ```
76
101
 
77
- **Truth** matcher:
102
+ **Type/class** matcher:
78
103
 
79
104
  ```ruby
80
- be_true = Matchi::Matcher::BeTrue.new
81
- be_true.matches? { true } # => true
105
+ matcher = Matchi::BeAnInstanceOf.new(:String)
106
+
107
+ matcher.expected # => :String
108
+ matcher.matches? { "foo" } # => true
82
109
  ```
83
110
 
84
- **Untruth** matcher:
111
+ **Change** matcher:
85
112
 
86
113
  ```ruby
87
- be_false = Matchi::Matcher::BeFalse.new
88
- be_false.matches? { false } # => true
89
- ```
114
+ object = []
115
+ matcher = Matchi::Change.new(object, :length).by(1)
90
116
 
91
- **Nil** matcher:
117
+ matcher.expected # => 1
118
+ matcher.matches? { object << 1 } # => true
92
119
 
93
- ```ruby
94
- be_nil = Matchi::Matcher::BeNil.new
95
- be_nil.matches? { nil } # => true
96
- ```
120
+ object = []
121
+ matcher = Matchi::Change.new(object, :length).by_at_least(1)
97
122
 
98
- **Type/class** matcher:
123
+ matcher.expected # => 1
124
+ matcher.matches? { object << 1 } # => true
99
125
 
100
- ```ruby
101
- be_an_instance_of = Matchi::Matcher::BeAnInstanceOf.new(:String)
102
- be_an_instance_of.matches? { "foo" } # => true
126
+ object = []
127
+ matcher = Matchi::Change.new(object, :length).by_at_most(1)
128
+
129
+ matcher.expected # => 1
130
+ matcher.matches? { object << 1 } # => true
131
+
132
+ object = "foo"
133
+ matcher = Matchi::Change.new(object, :to_s).from("foo").to("FOO")
134
+
135
+ matcher.expected # => "FOO"
136
+ matcher.matches? { object.upcase! } # => true
137
+
138
+ object = "foo"
139
+ matcher = Matchi::Change.new(object, :to_s).to("FOO")
140
+
141
+ matcher.expected # => "FOO"
142
+ matcher.matches? { object.upcase! } # => true
103
143
  ```
104
144
 
105
145
  **Satisfy** matcher:
106
146
 
107
147
  ```ruby
108
- satisfy = Matchi::Matcher::Satisfy.new { |value| value == 42 }
109
- satisfy.matches? { 42 } # => true
148
+ matcher = Matchi::Satisfy.new { |value| value == 42 }
149
+
150
+ matcher.expected # => #<Proc:0x00007fbaafc65540>
151
+ matcher.matches? { 42 } # => true
110
152
  ```
111
153
 
112
154
  ### Custom matchers
113
155
 
114
- Custom matchers can easily be defined for expressing expectations.
115
- They can be any Ruby class that responds to `matches?` instance method with a block.
156
+ Custom matchers can easily be added to express more specific expectations.
116
157
 
117
158
  A **Be the answer** matcher:
118
159
 
119
160
  ```ruby
120
161
  module Matchi
121
- module Matcher
122
- class BeTheAnswer < ::Matchi::Matcher::Base
123
- def matches?
124
- 42.equal?(yield)
125
- end
162
+ class BeTheAnswer
163
+ def expected
164
+ 42
165
+ end
166
+
167
+ def matches?
168
+ expected.equal?(yield)
126
169
  end
127
170
  end
128
171
  end
129
172
 
130
- be_the_answer = Matchi::Matcher::BeTheAnswer.new
131
- be_the_answer.matches? { 42 } # => true
173
+ matcher = Matchi::BeTheAnswer.new
174
+
175
+ matcher.expected # => 42
176
+ matcher.matches? { 42 } # => true
132
177
  ```
133
178
 
134
179
  A **Be prime** matcher:
@@ -137,60 +182,42 @@ A **Be prime** matcher:
137
182
  require "prime"
138
183
 
139
184
  module Matchi
140
- module Matcher
141
- class BePrime < ::Matchi::Matcher::Base
142
- def matches?
143
- Prime.prime?(yield)
144
- end
185
+ class BePrime
186
+ attr_reader :expected
187
+
188
+ def matches?
189
+ Prime.prime?(yield)
145
190
  end
146
191
  end
147
192
  end
148
193
 
149
- be_prime = Matchi::Matcher::BePrime.new
150
- be_prime.matches? { 42 } # => false
194
+ matcher = Matchi::BePrime.new
195
+
196
+ matcher.expected # => nil
197
+ matcher.matches? { 42 } # => false
151
198
  ```
152
199
 
153
200
  A **Start with** matcher:
154
201
 
155
202
  ```ruby
156
203
  module Matchi
157
- module Matcher
158
- class StartWith < ::Matchi::Matcher::Base
159
- def initialize(expected)
160
- super()
161
- @expected = expected
162
- end
163
-
164
- def matches?
165
- Regexp.new(/\A#{expected}/).match?(yield)
166
- end
167
- end
168
- end
169
- end
204
+ class StartWith
205
+ attr_reader :expected
170
206
 
171
- start_with = Matchi::Matcher::StartWith.new("foo")
172
- start_with.matches? { "foobar" } # => true
173
- ```
174
-
175
- ### Helper methods
176
-
177
- For convenience, it is possible to instantiate a matcher with a method rather than with its class.
178
- To do so, the `Helper` module can be included like this:
179
-
180
- ```ruby
181
- require "matchi/helper"
207
+ def initialize(expected)
208
+ @expected = expected
209
+ end
182
210
 
183
- class MatcherCollection
184
- include ::Matchi::Helper
211
+ def matches?
212
+ Regexp.new(/\A#{expected}/).match?(yield)
213
+ end
214
+ end
185
215
  end
186
- ```
187
216
 
188
- The set of loaded matcher then becomes accessible via a dynamically generated instance method, like these:
217
+ matcher = Matchi::StartWith.new("foo")
189
218
 
190
- ```ruby
191
- matcher = MatcherCollection.new
192
- matcher.equal(42).matches? { 44 } # => false
193
- matcher.be_an_instance_of(:String).matches? { "안녕하세요" } # => true
219
+ matcher.expected # => "foo"
220
+ matcher.matches? { "foobar" } # => true
194
221
  ```
195
222
 
196
223
  ## Contact
@@ -204,7 +231,7 @@ __Matchi__ follows [Semantic Versioning 2.0](https://semver.org/).
204
231
 
205
232
  ## License
206
233
 
207
- The [gem](https://rubygems.org/gems/matchi) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
234
+ 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).
208
235
 
209
236
  ***
210
237
 
data/lib/matchi.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Namespace for the Matchi library.
3
+ # A collection of damn simple expectation matchers.
4
4
  #
5
5
  # @api public
6
6
  module Matchi
7
7
  end
8
8
 
9
- require_relative File.join("matchi", "matcher")
9
+ Dir[File.join(File.dirname(__FILE__), "matchi", "*.rb")].each do |fname|
10
+ require_relative fname
11
+ end
data/lib/matchi/be.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matchi
4
+ # *Identity* matcher.
5
+ class Be
6
+ # @return [#equal?] The expected identical object.
7
+ attr_reader :expected
8
+
9
+ # Initialize the matcher with an object.
10
+ #
11
+ # @example
12
+ # require "matchi/be"
13
+ #
14
+ # Matchi::Be.new(:foo)
15
+ #
16
+ # @param expected [#equal?] The expected identical object.
17
+ def initialize(expected)
18
+ @expected = expected
19
+ end
20
+
21
+ # Boolean comparison between the actual value and the expected value.
22
+ #
23
+ # @example
24
+ # require "matchi/be"
25
+ #
26
+ # matcher = Matchi::Be.new(:foo)
27
+ #
28
+ # matcher.expected # => :foo
29
+ # matcher.matches? { :foo } # => true
30
+ #
31
+ # @yieldreturn [#object_id] The actual value to compare to the expected
32
+ # one.
33
+ #
34
+ # @return [Boolean] Comparison between actual and expected values.
35
+ def matches?
36
+ expected.equal?(yield)
37
+ end
38
+
39
+ # A string containing a human-readable representation of the matcher.
40
+ def inspect
41
+ "#{self.class}(#{expected.inspect})"
42
+ end
43
+
44
+ # Returns a string representing the matcher.
45
+ def to_s
46
+ "be #{expected.inspect}"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matchi
4
+ # *Type/class* matcher.
5
+ class BeAnInstanceOf
6
+ # @return [Symbol] The expected class name.
7
+ attr_reader :expected
8
+
9
+ # Initialize the matcher with (the name of) a class or module.
10
+ #
11
+ # @example
12
+ # require "matchi/be_an_instance_of"
13
+ #
14
+ # Matchi::BeAnInstanceOf.new(String)
15
+ #
16
+ # @param expected [Class, #to_s] The expected class name.
17
+ def initialize(expected)
18
+ @expected = String(expected).to_sym
19
+ end
20
+
21
+ # Boolean comparison between the class of the actual value and the
22
+ # expected class.
23
+ #
24
+ # @example
25
+ # require "matchi/be_an_instance_of"
26
+ #
27
+ # matcher = Matchi::BeAnInstanceOf.new(String)
28
+ #
29
+ # matcher.expected # => :String
30
+ # matcher.matches? { "foo" } # => true
31
+ #
32
+ # @yieldreturn [#class] the actual value to compare to the expected one.
33
+ #
34
+ # @return [Boolean] Comparison between actual and expected values.
35
+ def matches?
36
+ self.class.const_get(expected).equal?(yield.class)
37
+ end
38
+
39
+ # A string containing a human-readable representation of the matcher.
40
+ def inspect
41
+ "#{self.class}(#{expected})"
42
+ end
43
+
44
+ # Returns a string representing the matcher.
45
+ def to_s
46
+ "be an instance of #{expected}"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("change", "by_at_least")
4
+ require_relative File.join("change", "by_at_most")
5
+ require_relative File.join("change", "by")
6
+ require_relative File.join("change", "from")
7
+ require_relative File.join("change", "to")
8
+
9
+ module Matchi
10
+ # Wraps the target of a change matcher.
11
+ class Change
12
+ # Initialize a wrapper of the change matcher with an object and the name of
13
+ # one of its methods.
14
+ #
15
+ # @example
16
+ # require "matchi/change"
17
+ #
18
+ # Matchi::Change.new("foo", :to_s)
19
+ #
20
+ # @param object [#object_id] An object.
21
+ # @param method [Symbol] The name of a method.
22
+ # @param args [Array] A list of arguments.
23
+ # @param kwargs [Hash] A list of keyword arguments.
24
+ def initialize(object, method, *args, **kwargs, &block)
25
+ @state = -> { object.send(method, *args, **kwargs, &block) }
26
+ end
27
+
28
+ # Specifies a minimum delta of the expected change.
29
+ #
30
+ # @example
31
+ # require "matchi/change"
32
+ #
33
+ # object = []
34
+ #
35
+ # change_wrapper = Matchi::Change.new(object, :length)
36
+ # change_wrapper.by_at_least(1)
37
+ #
38
+ # @param expected [#object_id] The minimum delta of the expected change.
39
+ #
40
+ # @return [#matches?] A *change by at least* matcher.
41
+ def by_at_least(expected)
42
+ ByAtLeast.new(expected, &@state)
43
+ end
44
+
45
+ # Specifies a maximum delta of the expected change.
46
+ #
47
+ # @example
48
+ # require "matchi/change"
49
+ #
50
+ # object = []
51
+ #
52
+ # change_wrapper = Matchi::Change.new(object, :length)
53
+ # change_wrapper.by_at_most(1)
54
+ #
55
+ # @param expected [#object_id] The maximum delta of the expected change.
56
+ #
57
+ # @return [#matches?] A *change by at most* matcher.
58
+ def by_at_most(expected)
59
+ ByAtMost.new(expected, &@state)
60
+ end
61
+
62
+ # Specifies the delta of the expected change.
63
+ #
64
+ # @example
65
+ # require "matchi/change"
66
+ #
67
+ # object = []
68
+ #
69
+ # change_wrapper = Matchi::Change.new(object, :length)
70
+ # change_wrapper.by(1)
71
+ #
72
+ # @param expected [#object_id] The delta of the expected change.
73
+ #
74
+ # @return [#matches?] A *change by* matcher.
75
+ def by(expected)
76
+ By.new(expected, &@state)
77
+ end
78
+
79
+ # Specifies the original value.
80
+ #
81
+ # @example
82
+ # require "matchi/change"
83
+ #
84
+ # change_wrapper = Matchi::Change.new("foo", :to_s)
85
+ # change_wrapper.from("foo")
86
+ #
87
+ # @param expected [#object_id] The original value.
88
+ #
89
+ # @return [#matches?] A *change from* wrapper.
90
+ def from(expected)
91
+ From.new(expected, &@state)
92
+ end
93
+
94
+ # Specifies the new value to expect.
95
+ #
96
+ # @example
97
+ # require "matchi/change"
98
+ #
99
+ # change_wrapper = Matchi::Change.new("foo", :to_s)
100
+ # change_wrapper.to("FOO")
101
+ #
102
+ # @param expected [#object_id] The new value to expect.
103
+ #
104
+ # @return [#matches?] A *change to* matcher.
105
+ def to(expected)
106
+ To.new(expected, &@state)
107
+ end
108
+ end
109
+ end