speq 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d4e8796522782ab6e954f384d21d6b6d1d5e6728c773c68e587873f4183173e
4
- data.tar.gz: f0e4a46ce5889abf250761d4f990a43a61792c6bade4e30e88fd7e9c45869699
3
+ metadata.gz: efc22fe2da50ca215a4ca412a768e04e70fe97ff535d50ab8bd38cf918676fbf
4
+ data.tar.gz: 2daeaccd796d8eaa2fd8369495675ffd7df8171fb7bdaed2529a781dfeb4544d
5
5
  SHA512:
6
- metadata.gz: 9a08b7857d12e9b57af750fb35db681884a7af03a6224424abf659ad2eb4fde5353c8dbb3b873ab6c9f0ed6ac90ae4732338150748def36ddda786ad1921845f
7
- data.tar.gz: e4ac157417ac3cda5a872b1b13a39ece575d437b10ea8fbca320d71ab383b255ca66be76085ce07bc3f20054d796dacfe17ad9a855632b10b44e21fe67c26681
6
+ metadata.gz: c6ed8070160429f4fa4badfda408e09a5d95900f5df0b6a3014c09a85496aa5ca6baca3efb76d0bff9b2ff9211b234133512df8d300ab833b4bd707831b53479
7
+ data.tar.gz: c61f81ead1d0e33220cd0e0697376a28cb4be9db73156670d4ab1d945cb06c491e13b27d105535470c552428f27a0b7eeaebfeb99963b565269026709790486b
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.6.5
data/.travis.yml CHANGED
@@ -3,5 +3,5 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.5.1
7
- before_install: gem install bundler -v 1.16.6
6
+ - 2.6.5
7
+ before_install: gem install bundler -v 1.17.2
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Speq
2
2
 
3
+ **Ready to use! Issue reports are welcome!**
4
+
3
5
  ## Build specs with fewer words
4
6
 
5
7
  Speq is a testing library for rapid prototyping in Ruby.
@@ -7,18 +9,8 @@ Speq is a testing library for rapid prototyping in Ruby.
7
9
  Speq favors simplicity and minimalism.
8
10
 
9
11
  ```ruby
10
- is(Array.new).empty?
11
- # Passed (1/1)
12
- # [] is empty.
13
- ```
14
-
15
- But also values flexibility.
16
-
17
- ```ruby
18
- speq(Array(nil), 'an array created by calling Array(nil)').eq?([nil])
19
-
20
- # Failed (1/1)
21
- # An array created by calling Array(nil) is NOT equal to [nil]. ( [] != [nil] )
12
+ is([]).empty?
13
+ # [] is empty.
22
14
  ```
23
15
 
24
16
  Speq is ideal whenever it would feel excessive to use one of the existing frameworks, but it's not enough to just print & inspect outputs.
@@ -33,271 +25,55 @@ gem 'speq'
33
25
 
34
26
  And then execute:
35
27
 
36
- bundle
37
-
38
- Or install it yourself as:
39
-
40
- gem install speq
41
-
42
- ## Design & Syntax
43
-
44
- Speq is loosely based on the given-when-then or arrange-act-assert testing pattern. Speq's design choices are guided by the competing motivations of having tests be as short and simple as possible while maintaining the flexibility to be as descriptive as needed.
45
-
46
- In contrast to similar tools, speqs are typically framed as _questions_ about a program's behavior rather than assertions about the desired behavior. The most basic test involves passing a string to the method `speq` followed by `pass?` and a block that evaluates strictly to true or false.
47
-
48
- ```ruby
49
- speq("Does Ruby properly calculate Euler's identity?").pass? do
50
- e = Math::E
51
- π = Math::PI
52
- i = (1i)
53
-
54
- e**(i*π) + 1 == 0
55
- end
56
-
57
- # Passed (1/1)
58
- # Testing 'does Ruby properly calculate Euler's identity?' passes.
59
- ```
60
-
61
- The above works, but it's not much shorter than existing solutions. More importantly, it's missing vital information about what action was taken, what the outcome was, and what assertion led to the success.
62
-
63
- Shown below is the basic two-part pattern for most speqs and the resulting report combining explicit and implicit descriptions:
64
-
65
- ```ruby
66
- speq(Math::E**((1i) * Math::PI ) + 1, 'e^iπ + 1').eq?(0)
67
- # Passed (1/1)
68
- # e^iπ + 1 is equal to 0. ( (0.0+0.0i) == 0 )
69
- ```
70
-
71
- ### Actions
72
-
73
- Descriptions for simple unit tests are often closely tied to the source code, and as such, Speq tries to eliminate them wherever possible. To automatically generate descriptions, the action or subject of interest is supplied to be evaluated dynamically.
74
-
75
- More specifically, we begin by setting up the program state and then explicitly supply the action being tested using the methods below. By breaking down the expression's method/message, arguments, and receiver, Speq can use the additional information to help generate an often sufficiently detailed description of the test. Actions are evaluated when they reach a [matcher](#matchers).
76
-
77
- - message: `does(*:symbol` or `is(*:symbol)`, default: `:itself`
78
- - arguments: `with(...)` or `of(...)`, default: `*[]`
79
- - receiver: `on(object, description(optional))`, default: `Object`
80
-
81
- ```ruby
82
- is(:prime?).of(2).true?
83
-
84
- on((1..4).to_a.reverse).does(:sort).eq?([1, 2, 3, 4])
85
-
86
- # .with() accepts arguments with the exact same format as any other method
87
- does(:map).with { |idx| idx * idx }.on(0..3).eq?([0, 1, 4, 9])
88
-
89
- # Passed (3/3)
90
- # prime? of 2 is true.
91
- # sort on [4, 3, 2, 1] equals [1, 2, 3, 4].
92
- # map with a block on 1..4 equals [0, 1, 4, 9].
93
- ```
94
-
95
- Chaining methods can be accomplished by providing multiple symbols. If an intermediate value needs to be tested or a method needs to be given arguments, this can be accomplished by using `.then(:next_method).with`. An example of this is shown below. An alternative, unconventional syntax for accomplishing the same can be found in the section on [syntactic sugar](#Sugar)
96
-
97
- ```ruby
98
- on(1..4)
99
- .does(:to_a, :shuffle, :sort)
100
- .eq?([1, 2, 3, 4])
101
- .then(:sort).with { | a, b | b <=> a }
102
- .eq?([4, 3, 2, 1])
103
-
104
- # Passed (1/1)
105
- # to_a on 1..4 then reverse (on [1, 2, 3, 4]) then sort (on [4, 3, 2, 1]) equals [1, 2, 3, 4].
28
+ ```sh
29
+ bundle
106
30
  ```
107
31
 
108
- ### Matchers
109
-
110
- All speq methods that end with a question mark are matchers.
111
-
112
- The matcher `pass?` is the most general and simply expects to be given a code block that evaluates to true or false. The output from the action is passed along as the first piped variable.
113
-
114
- ```ruby
115
- does(:strip)
116
- .on(' speq ')
117
- .pass? { |str| str == 'speq' && !str.include?(' ') }
118
-
119
- does(:rand)
120
- .pass? { |val| val.is_a?(Float) }
121
-
122
- does(:prime?)
123
- .with(-1)
124
- .raise?('Prime is not defined for negative numbers')
125
-
126
- on(User)
127
- .does(:new)
128
- .with(id: 1, name: 'user name')
129
- .have?(:id)
130
- ```
131
-
132
- #### Prefixes and Compound Matchers
133
-
134
- Multiple matchers can follow an action. By default, all matchers must pass for the test to be considered successful. To run multiple tests on the same action, see the section on [test subjects](#subjects)
135
-
136
- The logical inverse of any matcher can be achieved by prefixing the matcher with `not_`.
137
-
138
- ```ruby
139
- # Equivalent to the previous speq
140
- does(:strip).on(' speq ').not_include?(' ').eq?('speq')
141
- ```
142
-
143
- For simple combinations, it's sufficient to prefix the second matcher with one of the following: `or_, nand_, nor_, xor_, xnor_`
144
-
145
- ```ruby
146
- arr = Array.new(10, rand.round)
147
- speq(arr, '10 element array filled with zeros or ones').has?(length: 100).all?(0).or_all?(1)
148
-
149
- # arr.length == 100 && (arr.all?(0) || arr.all?(1))
150
- ```
151
-
152
- Although it's best to avoid complex combinations of matchers, logical operators can be combined unambiguously if absolutely needed by chaining operators with prefix notation.
153
-
154
- ```ruby
155
- default = rand.round(1) <=> 0.5
156
- length = default.zero? ? 0 : 100
32
+ Or install it yourself as:
157
33
 
158
- arr = Array.new(length, default)
159
- speq(arr, 'Either empty, all 1, or all -1').or?.empty?.xor?.include?(-1).include?(1)
34
+ ```sh
35
+ gem install speq
160
36
  ```
161
37
 
162
- #### Generated & built-in matchers
163
-
164
- If the object being tested responds to the method ending with `?`, then an appropriate matcher is automatically generated. As a result, existing methods such as `instance_of?`, `nil?`, or `empty?` can be used despite the fact that they are not in the list below.
165
-
166
- ```ruby
167
- pass?(*description, &block)
168
- has?(*:message, **(message: val)) # alias: have?
169
-
170
- eq?(obj) # result == obj
171
- case?(obj) # obj === result
172
-
173
- true?
174
- false?
175
- truthy?
176
- falsy?
177
-
178
- raise?('The following error message')
179
- raise?(ErrorClass)
38
+ ## Design & Syntax
180
39
 
181
- # Compound matchers
182
- either?(*matcher) # matcher1 || matcher2 || ...
183
- neither?(*matcher) # !matcher1 || !matcher2 || ...
40
+ Speq's design choices are guided by the competing motivations of having tests be as short and simple as possible while maintaining the flexibility to be as descriptive as needed. In contrast to similar tools, speqs are typically framed as _questions_ about a program's behavior rather than assertions about the desired behavior.
184
41
 
185
- ```
42
+ Descriptions for simple unit tests are often closely tied to the source code, and as such, Speq tries to eliminate them wherever possible. By breaking down an expression's method/message, arguments, and receiver, Speq can use the additional information to help generate an often sufficiently detailed description of the test.
186
43
 
187
- #### Matcher name precedence
44
+ - receiver: `on(object [, description])`
45
+ - message: `does(symbol [, description])`
46
+ - arguments: `with(*args , &block)` or `of(*args, &block)`
47
+ - question: `*?(*args, &block)`
188
48
 
189
- Parsing the name of a matcher gives highest precedence to the object being tested. For example, consider the following test:
49
+ Note that Speq executes expressions immediately, avoiding the unintuitive execution order of most testing libraries.
190
50
 
191
51
  ```ruby
192
- class Display
193
- attr_reader :type
194
-
195
- def initialize(type = :case)
196
- @type = type.to_sym
197
- end
52
+ speq('Example test') do
53
+ on((1..4).to_a.shuffle, 'a shuffled array').does(:sort).eq?([1, 2, 3, 4])
198
54
 
199
- def case?
200
- @type == :case
55
+ does(:reverse) do
56
+ on('3 2 1').eq?('1 2 3')
57
+ on([3, 2, 1]).eq?([1, 2, 3])
201
58
  end
202
- end
203
-
204
- window_display = Display.new('window')
205
-
206
- speq(window_display).not_case? # Will use Display#case
207
- on(window_display).is(:type).case?(Symbol) # Will use Speq#case?
208
- ```
209
-
210
- To resolve `not_case?`, speq will do the following:
211
-
212
- 1. Checks for the prefix `speq_`, reserved in case of name conflicts.
213
- 2. Generates a matcher if the object responds to `not_case?`.
214
- 3. Generates a matcher if the object responds to `case?` and applies prefixes.
215
- 4. Uses the built-in `case?` and applies prefixes.
216
-
217
- ### Additional Functionality
218
-
219
- #### Object Descriptions
220
-
221
- It may be advantageous to describe an object in advance such that, from there on, the report will automatically include the object description anytime that object is encountered.
222
-
223
- ```ruby
224
- MyClass = Class.new
225
-
226
- call(MyClass, 'my class, an instance of the class Class')
227
- speq(MyClass).instance_of?(Class)
228
-
229
- # call tracks both object identity and object equality
230
- my_arr = []
231
- call(my_arr, 'a specific empty array')
232
- call([], 'an empty array')
233
-
234
- speq(my_arr).eq?([])
235
-
236
- # Passed (2/2)
237
- # 'my class, an instance of the class Class' is an instance of the class Class.
238
- # 'my specific empty array' equals 'an empty array'
239
- ```
240
-
241
- #### Fakes
242
59
 
243
- Fakes are Speq's simple implementation of a test double. Most closely resembling a stub, fakes provide canned or computed responses, allowing for additional test isolation for objects that rely on objects not within the scope of testing.
60
+ with(&->(idx) { idx * idx }).does(:map).on(0..3).eq?([0, 1, 4, 9])
244
61
 
245
- ```ruby
246
- fake_bank = fake(
247
- balance: 5000,
248
- print_balance: '$50.00',
249
- withdraw: proc {|amount| [50, amount].min }
250
- )
251
- ```
252
-
253
- #### Subjects
254
-
255
- Speq has the distinct advantage of making it particularly easy to run many similar tests on the same subject.
256
- Opening a block on an action that has not been clo
257
-
258
- ```ruby
259
- not_prime = [0, 1, 4, 6, 8, 9]
260
- prime = [2, 3, 5, 7]
261
-
262
- #
263
- does(:prime?) do
264
- with[not_prime].false?
265
- with[prime].true?
266
- end
267
-
268
- does(:my_sort) do
269
- small_array = [3, 2, 1]
270
- large_array = Array.new(10**6) {rand}
271
-
272
- on(small_array).eq?(small_array.sort)
273
- on(large_array).eq?(large_array.sort)
62
+ is(:rand).a?(Float) # symbol -> does
63
+ is([]).empty? # not symbol -> on
274
64
  end
275
65
  ```
276
66
 
277
- #### Sugar
278
-
279
- ```ruby
280
- # Action-Question Chains
281
- on('3 2 1')
282
- << :split
283
- << has?(length: 3)
284
- << :map << with(&:to_i)
285
- << :reduce << with(&:+)
286
- << eq?(6)
287
-
288
- # Can start with the usual syntax and switch when convenient
289
- on(1..4).does(:to_a, :shuffle, :sort).eq?([1, 2, 3, 4])
290
- << :sort << with { | a, b | b <=> a } << eq?([4, 3, 2, 1])
291
-
292
- # Broadcasting
293
- def add(a, b)
294
- a + b
295
- end
296
-
297
- a = [1, 2, 3]
298
- b = [4, 5, 6]
299
-
300
- does(:add).with[a, b].eq?([5, 7, 9])
67
+ ```
68
+ ✔ Example test
69
+ ✔ sort on a shuffled array equals [1, 2, 3, 4].
70
+ reverse...
71
+ ✔ "3 2 1" equals "1 2 3".
72
+ [3, 2, 1] equals [1, 2, 3].
73
+ map(&{ ... }) on 0..3 equals [0, 1, 4, 9].
74
+ rand is a Float.
75
+ [] is empty.
76
+ pass: 5, fail: 0, errors: 0
301
77
  ```
302
78
 
303
79
  ## Usage
@@ -312,7 +88,7 @@ To run individual files, specify a list of speq file prefixes. For example, to r
312
88
 
313
89
  speq example sample
314
90
 
315
- Speq files only `require 'speq'` by default, so any external code being tested will still need to be required.
91
+ Note that you don't need to require 'speq' within a speq file if running from the CLI.
316
92
 
317
93
  ### Anywhere: Speq.test
318
94
 
@@ -321,37 +97,11 @@ Speq can be used anywhere as long as it is required and all tests are written wi
321
97
  ```ruby
322
98
  require 'speq'
323
99
 
324
- Speq.test(self) do | context |
325
- # You can pass the surrounding context if needed.
326
- # Calling 'report' prints the tests seen so far to $stdout.
327
-
328
- # By default, Speq.test Returns the instance of Speq::Test created here.
329
- # Using the return keyword, you can choose to return the following instead:
330
- # 'return score': returns the proportion of tests passed as a Rational number
331
- # 'return pass?': returns true or false for whether all the tests passed
332
- end
333
- ```
334
-
335
- Speq can also be included to be used in a more direct manner. Here is an example of using Speq within another class for added functionality.
336
-
337
- ```ruby
338
- require 'speq'
339
-
340
- class Exam
341
- include Speq
342
-
343
- def initialize(questions)
344
- @questions = questions
345
- end
346
-
347
- def grade
348
- @questions.each do | question |
349
- speq(question.response, question.to_s).eq?(question.answer)
350
- end
351
-
352
- score
353
- end
100
+ speq("testing anywhere") do
101
+ ...
354
102
  end
103
+ # '.score': returns the proportion of tests passed as a Rational number
104
+ # '.pass?': returns true or false for whether all the tests passed
355
105
  ```
356
106
 
357
107
  ## Contributing
data/bin/console CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'pry'
5
-
6
- require 'speq'
7
- include Speq
6
+ load 'speq.rb'
7
+ load 'speq/cli.rb'
8
8
 
9
9
  Pry.start
data/exe/speq CHANGED
@@ -1,3 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'speq'
2
5
  require 'speq/cli'
3
- Speq::CLI.new(ARGV)
6
+
7
+ Speq::CLI.run_and_report(ARGV)
data/lib/speq/cli.rb CHANGED
@@ -1,24 +1,45 @@
1
- require 'speq'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'find'
3
- require 'colorize'
4
+ require 'date'
5
+ require 'pry'
4
6
 
5
- # Provides a CLI for running Speq
6
7
  module Speq
7
- class CLI
8
- def initialize(cli_args)
9
- @files = find_files(cli_args)
10
- run
8
+ # Includes useful methods for running Speq from a CLI
9
+ module CLI
10
+ def self.run(cli_args)
11
+ run_tests(find_files(cli_args))
12
+ end
13
+
14
+ def self.run_and_report(cli_args)
15
+ print_report(run(cli_args))
16
+ end
17
+
18
+ def self.run_tests(paths)
19
+ paths.map { |file| file_test(file) }
20
+ end
21
+
22
+ def self.file_test(file)
23
+ speq(file_description(file)) { instance_eval(File.read(file), file) }
11
24
  end
12
25
 
13
- def find_files(file_prefixes)
14
- file_prefixes << '*' if file_prefixes.empty?
26
+ def self.file_description(path)
27
+ path
28
+ .split('/').last
29
+ .delete_suffix('_speq.rb')
30
+ .split('_')
31
+ .map(&:capitalize)
32
+ .join(' ')
33
+ end
15
34
 
16
- Dir.glob("#{Dir.pwd}/**/{#{file_prefixes.join(',')}}_speq.rb")
35
+ def self.find_files(cli_args)
36
+ prefixes = cli_args.empty? ? ['*'] : cli_args
37
+ glob_pattern = "#{Dir.pwd}/**/{#{prefixes.join(',')}}_speq.rb"
38
+ Dir.glob(glob_pattern)
17
39
  end
18
40
 
19
- def run
20
- @files.each { |file| Speq.module_eval(File.read(file), file) }
21
- Speq.report
41
+ def self.print_report(tests)
42
+ puts tests
22
43
  end
23
44
  end
24
45
  end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'speq/values'
4
+
5
+ module Speq
6
+ # Error class for Question
7
+ class QuestionError < StandardError
8
+ def initialize(question_name, error)
9
+ super("#{question_name}: #{error}")
10
+ end
11
+ end
12
+
13
+ # Records the question name, result, and constructs the corresponding matcher
14
+ class Question
15
+ def self.question?(method_name)
16
+ method_name.to_s.end_with?('?')
17
+ end
18
+
19
+ attr_reader :question_name, :matcher, :result
20
+
21
+ def initialize(question_name, result, matcher)
22
+ @question_name = question_name
23
+ @result = result
24
+ @matcher = matcher
25
+ end
26
+
27
+ def phrase
28
+ matcher.phrase(result)
29
+ rescue StandardError => e
30
+ QuestionError.new(@question_name, e.inspect).to_s
31
+ end
32
+
33
+ def outcome
34
+ matcher.match?(result) ? Outcome.pass : Outcome.fail
35
+ rescue StandardError
36
+ Outcome.error
37
+ end
38
+
39
+ def self.for(result, question_name, *args, &block)
40
+ matcher = Matcher.for(question_name, *args, &block)
41
+
42
+ Question.new(
43
+ question_name,
44
+ result,
45
+ matcher
46
+ )
47
+ end
48
+ end
49
+
50
+ # Boolean #match? for a given result and descriptive phrase
51
+ class Matcher
52
+ def initialize(matcher, pass_phrase, fail_phrase)
53
+ @matcher = matcher
54
+ @pass_phrase = pass_phrase
55
+ @fail_phrase = fail_phrase
56
+ end
57
+
58
+ def match?(actual)
59
+ @matcher[actual]
60
+ end
61
+
62
+ def phrase(actual)
63
+ @matcher[actual] ? @pass_phrase : @fail_phrase
64
+ end
65
+
66
+ def self.matcher_is(match, thing)
67
+ new(match, "is #{thing}", "is not #{thing}")
68
+ end
69
+
70
+ def self.for(question_name, *args, &block)
71
+ if respond_to?(question_name)
72
+ send(question_name, *args, &block)
73
+ else
74
+ result_matcher(question_name, *args, &block)
75
+ end
76
+ end
77
+
78
+ def self.match?(description, &block)
79
+ new(block, "matches #{description}", "does not match #{description}")
80
+ end
81
+
82
+ def self.result_matcher(question_name, *args, &block)
83
+ new(
84
+ lambda do |obj|
85
+ if obj.respond_to?(question_name)
86
+ return obj.send(question_name, *args, &block)
87
+ end
88
+
89
+ raise "No question called #{question_name.inspect} or existing method on #{obj.inspect}"
90
+ end,
91
+ "is #{question_name[0..-2]}",
92
+ "is not #{question_name[0..-2]}"
93
+ )
94
+ end
95
+
96
+ def self.eq?(expected_value)
97
+ new(
98
+ ->(result) { expected_value == result },
99
+ "equals #{expected_value.inspect}",
100
+ "does not equal #{expected_value.inspect}"
101
+ )
102
+ end
103
+
104
+ def self.true?
105
+ matcher_is(->(result) { result == true }, 'true')
106
+ end
107
+
108
+ def self.truthy?
109
+ matcher_is(->(actual_value) { actual_value ? true : false }, 'truthy')
110
+ end
111
+
112
+ def self.falsy?
113
+ matcher_is(->(actual_value) { actual_value ? false : true }, 'falsey')
114
+ end
115
+
116
+ def self.a?(type)
117
+ matcher_is(->(val) { val.is_a?(type) }, "a #{type}")
118
+ end
119
+
120
+ def self.have?(*symbols, **key_value_pairs)
121
+ new(
122
+ lambda do |object|
123
+ symbols.each do |symbol|
124
+ return false unless object.respond_to?(symbol)
125
+ end
126
+ key_value_pairs.each do |key, value|
127
+ return false unless object.send(key) == value
128
+ end
129
+ end,
130
+ "has #{symbols.empty? ? nil : symbols}#{key_value_pairs}",
131
+ "doesn't have #{symbols.empty? ? nil : symbols}#{key_value_pairs}"
132
+ )
133
+ end
134
+
135
+ def self.raise?(expected_except)
136
+ case expected_except
137
+ when Class
138
+ raise_class(expected_except)
139
+ when String
140
+ raise_message(expected_except)
141
+ else
142
+ raise ArgumentError
143
+ end
144
+ end
145
+
146
+ def self.raise_class(expected_error)
147
+ Matcher.new(
148
+ ->(actual_except) { actual_except.class <= expected_error },
149
+ "raises #{expected_error}",
150
+ "does not raise #{expected_error}"
151
+ )
152
+ end
153
+
154
+ def self.raise_message(expected_message)
155
+ Matcher.new(
156
+ ->(actual_except) { actual_except.message == expected_message },
157
+ "raises #{expected_message.inspect}",
158
+ "does not raise #{expected_message.inspect}"
159
+ )
160
+ end
161
+ end
162
+ end