r_spec 1.0.0.beta3 → 1.0.0.beta8

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.
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ # Send log messages to the console.
5
+ #
6
+ # @api private
7
+ module Console
8
+ # @param report [::Expresenter::Pass] Passed expectation result presenter.
9
+ #
10
+ # @see https://github.com/fixrb/expresenter
11
+ #
12
+ # @return [nil] Add a colored message to `$stdout`.
13
+ def self.passed_spec(report)
14
+ puts report.colored_string
15
+ end
16
+
17
+ # @param report [::Expresenter::Fail] Failed expectation result presenter.
18
+ #
19
+ # @see https://github.com/fixrb/expresenter
20
+ #
21
+ # @raise [SystemExit] Terminate execution immediately with colored message.
22
+ def self.failed_spec(report)
23
+ abort report.colored_string
24
+ end
25
+
26
+ # The Ruby source filename and line number containing this method or nil if
27
+ # this method was not defined in Ruby (i.e. native).
28
+ #
29
+ # @param filename [String, nil] The Ruby source filename.
30
+ # @param line [Integer, nil] The Ruby source line number.
31
+ #
32
+ # @return [String] The Ruby source filename and line number associated with
33
+ # the evaluated spec.
34
+ def self.source(filename, line)
35
+ puts [filename, line].compact.join(":")
36
+ end
37
+ end
38
+ end
data/lib/r_spec/dsl.rb CHANGED
@@ -1,124 +1,345 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "matchi/rspec"
4
- require "securerandom"
3
+ require_relative "console"
4
+ require_relative "error"
5
+ require_relative "expectation_helper"
5
6
 
6
7
  module RSpec
7
8
  # Abstract class for handling the domain-specific language.
8
- class DSL
9
+ class Dsl
10
+ BEFORE_METHOD = :initialize
11
+ AFTER_METHOD = :terminate
12
+
13
+ private_constant :BEFORE_METHOD, :AFTER_METHOD
14
+
15
+ # Executes the given block before each spec in the current context runs.
16
+ #
17
+ # @example
18
+ # require "r_spec"
19
+ #
20
+ # RSpec.describe Integer do
21
+ # before do
22
+ # @value = 123
23
+ # end
24
+ #
25
+ # it { expect(@value).to be 123 }
26
+ #
27
+ # describe "nested" do
28
+ # before do
29
+ # @value -= 81
30
+ # end
31
+ #
32
+ # it { expect(@value).to be 42 }
33
+ # end
34
+ #
35
+ # it { expect(@value).to be 123 }
36
+ # end
37
+ #
38
+ # # Output to the console
39
+ # # Success: expected to be 123.
40
+ # # Success: expected to be 42.
41
+ # # Success: expected to be 123.
42
+ #
9
43
  # @param block [Proc] The content to execute at the class initialization.
10
44
  def self.before(&block)
11
- define_method(:initialize) do |*args, **kwargs|
45
+ define_method(BEFORE_METHOD) do
46
+ super()
47
+ instance_eval(&block)
48
+ end
49
+
50
+ private BEFORE_METHOD
51
+ end
52
+
53
+ # Executes the given block after each spec in the current context runs.
54
+ #
55
+ # @example
56
+ # require "r_spec"
57
+ #
58
+ # RSpec.describe Integer do
59
+ # after do
60
+ # puts "That is the answer to everything."
61
+ # end
62
+ #
63
+ # it { expect(42).to be 42 }
64
+ # end
65
+ #
66
+ # # Output to the console
67
+ # # Success: expected to be 42.
68
+ # # That is the answer to everything.
69
+ #
70
+ # @param block [Proc] The content to execute at the class initialization.
71
+ def self.after(&block)
72
+ define_method(AFTER_METHOD) do
73
+ instance_exec(&block)
12
74
  super()
13
- instance_exec(*args, **kwargs, &block)
14
75
  end
76
+
77
+ private AFTER_METHOD
15
78
  end
16
79
 
17
- # @param block [Proc] The content of the method to define.
18
- # @return [Symbol] A protected method that define the block content.
19
- def self.let(name, &block)
20
- protected define_method(name.to_sym, &block)
80
+ # Sets a user-defined property.
81
+ #
82
+ # @example
83
+ # require "r_spec"
84
+ #
85
+ # RSpec.describe "Name stories" do
86
+ # let(:name) { "Bob" }
87
+ #
88
+ # it { expect(name).to eq "Bob" }
89
+ #
90
+ # context "with last name" do
91
+ # let(:name) { "#{super()} Smith" }
92
+ #
93
+ # it { expect(name).to eq "Bob Smith" }
94
+ # end
95
+ # end
96
+ #
97
+ # # Output to the console
98
+ # # Success: expected to eq "Bob".
99
+ # # Success: expected to eq "Bob Smith".
100
+ #
101
+ # @param name [String, Symbol] The name of the property.
102
+ # @param block [Proc] The content of the method to define.
103
+ #
104
+ # @return [Symbol] A private method that define the block content.
105
+ def self.let(name, *args, **kwargs, &block)
106
+ raise Error::ReservedMethod if [BEFORE_METHOD, AFTER_METHOD].include?(name.to_sym)
107
+
108
+ private define_method(name, *args, **kwargs, &block)
21
109
  end
22
110
 
111
+ # Sets a user-defined property named {#subject}.
112
+ #
113
+ # @example
114
+ # require "r_spec"
115
+ #
116
+ # RSpec.describe Array do
117
+ # subject { [1, 2, 3] }
118
+ #
119
+ # it "has the prescribed elements" do
120
+ # expect(subject).to eq([1, 2, 3])
121
+ # end
122
+ # end
123
+ #
124
+ # # Output to the console
125
+ # # Success: expected to eq [1, 2, 3].
126
+ #
23
127
  # @param block [Proc] The subject to set.
24
- # @return [Symbol] A `subject` method that define the block content.
128
+ # @return [Symbol] A {#subject} method that define the block content.
25
129
  def self.subject(&block)
26
130
  let(__method__, &block)
27
131
  end
28
132
 
29
- # Describe a set of expectations.
133
+ # Defines an example group that describes a unit to be tested.
30
134
  #
31
- # @param const [Module, #object_id] A module to include in block context.
135
+ # @example
136
+ # require "r_spec"
137
+ #
138
+ # RSpec.describe String do
139
+ # describe "+" do
140
+ # it("concats") { expect("foo" + "bar").to eq "foobar" }
141
+ # end
142
+ # end
143
+ #
144
+ # # Output to the console
145
+ # # Success: expected to eq "foobar".
146
+ #
147
+ # @param const [Module, String] A module to include in block context.
32
148
  # @param block [Proc] The block to define the specs.
33
149
  def self.describe(const, &block)
34
- desc = Test.const_set(random_test_const_name, ::Class.new(self))
35
-
36
- if const.is_a?(::Module)
37
- desc.define_method(:described_class) { const }
38
- desc.send(:protected, :described_class)
39
- end
40
-
150
+ desc = ::Class.new(self)
151
+ desc.let(:described_class) { const } if const.is_a?(::Module)
41
152
  desc.instance_eval(&block)
42
- desc
43
153
  end
44
154
 
45
- # Add `context` to the DSL.
46
- singleton_class.send(:alias_method, :context, :describe)
155
+ # Defines an example group that establishes a specific context, like _empty
156
+ # array_ versus _array with elements_.
157
+ #
158
+ # It is functionally equivalent to {.describe}.
159
+ #
160
+ # @example
161
+ # require "r_spec"
162
+ #
163
+ # RSpec.describe "web resource" do
164
+ # context "when resource is not found" do
165
+ # pending "responds with 404"
166
+ # end
167
+ #
168
+ # context "when resource is found" do
169
+ # pending "responds with 200"
170
+ # end
171
+ # end
172
+ #
173
+ # # Output to the console
174
+ # # Warning: responds with 404.
175
+ # # Warning: responds with 200.
176
+ #
177
+ # @param _description [String] A description that usually begins with
178
+ # "when", "with" or "without".
179
+ # @param block [Proc] The block to define the specs.
180
+ def self.context(_description = nil, &block)
181
+ desc = ::Class.new(self)
182
+ desc.instance_eval(&block)
183
+ end
47
184
 
48
- # Evaluate an expectation.
185
+ # Defines a concrete test case.
186
+ #
187
+ # The test is performed by the block supplied to `&block`.
188
+ #
189
+ # @example The integer after 41
190
+ # require "r_spec"
191
+ #
192
+ # RSpec.describe Integer do
193
+ # it { expect(41.next).to be 42 }
194
+ # end
195
+ #
196
+ # # Output to the console
197
+ # # Success: expected to be 42.
198
+ #
199
+ # @example A division by zero
200
+ # require "r_spec"
201
+ #
202
+ # RSpec.describe Integer do
203
+ # subject { 41 }
204
+ #
205
+ # it { is_expected.to be_an_instance_of described_class }
206
+ #
207
+ # it "raises an error" do
208
+ # expect { subject / 0 }.to raise_exception ZeroDivisionError
209
+ # end
210
+ # end
211
+ #
212
+ # # Output to the console
213
+ # # Success: expected 41 to be an instance of Integer.
214
+ # # Success: divided by 0.
49
215
  #
216
+ # It can be used inside a {.describe} or {.context} section.
217
+ #
218
+ # @param _name [String, nil] The name of the spec.
50
219
  # @param block [Proc] An expectation to evaluate.
51
220
  #
52
221
  # @raise (see ExpectationTarget::Base#result)
53
222
  # @return (see ExpectationTarget::Base#result)
54
223
  def self.it(_name = nil, &block)
55
- raise ::ArgumentError, "Missing block" unless block
224
+ raise ::ArgumentError, "Missing example block" unless block
56
225
 
57
- puts "\e[37m#{block.source_location.join(':')}\e[0m"
226
+ example = ::Class.new(self) { include ExpectationHelper::It }.new
227
+ example.instance_eval(&block)
228
+ rescue ::SystemExit
229
+ Console.source(*block.source_location)
58
230
 
59
- i = example.new
60
- i.instance_eval(&block)
231
+ exit false
232
+ ensure
233
+ example&.send(AFTER_METHOD)
61
234
  end
62
235
 
63
- # @private
236
+ # Use the {.its} method to define a single spec that specifies the actual
237
+ # value of an attribute of the subject using
238
+ # {ExpectationHelper::Its#is_expected}.
64
239
  #
65
- # @return [Class<DSL>] The class of the example to be tested.
66
- def self.example
67
- # Dynamic creation of an example class.
68
- ::Class.new(self) do
69
- # Include a collection of matchers.
70
- include ::Matchi::Helper
71
-
72
- private
240
+ # @example The integer after 41
241
+ # require "r_spec"
242
+ #
243
+ # RSpec.describe Integer do
244
+ # subject { 41 }
245
+ #
246
+ # its(:next) { is_expected.to be 42 }
247
+ # end
248
+ #
249
+ # # Output to the console
250
+ # # Success: expected to be 42.
251
+ #
252
+ # @example A division by zero
253
+ # require "r_spec"
254
+ #
255
+ # RSpec.describe Integer do
256
+ # subject { 41 }
257
+ #
258
+ # its(:/, 0) { is_expected.to raise_exception ZeroDivisionError }
259
+ # end
260
+ #
261
+ # # Output to the console
262
+ # # Success: divided by 0.
263
+ #
264
+ # @example A spec without subject
265
+ # require "r_spec"
266
+ #
267
+ # RSpec.describe Integer do
268
+ # its(:boom) { is_expected.to raise_exception RSpec::Error::UndefinedSubject }
269
+ # end
270
+ #
271
+ # # Output to the console
272
+ # # Success: subject not explicitly defined.
273
+ #
274
+ # @param attribute [String, Symbol] The property to call to subject.
275
+ # @param args [Array] An optional list of arguments.
276
+ # @param kwargs [Hash] An optional list of keyword arguments.
277
+ # @param block [Proc] An expectation to evaluate.
278
+ #
279
+ # @raise (see ExpectationTarget::Base#result)
280
+ # @return (see ExpectationTarget::Base#result)
281
+ def self.its(attribute, *args, **kwargs, &block)
282
+ raise ::ArgumentError, "Missing example block" unless block
73
283
 
74
- # Wraps the target of an expectation with the actual value.
75
- #
76
- # @param actual [#object_id] The actual value.
77
- #
78
- # @return [ExpectationTarget] The target of the expectation.
79
- def expect(actual = self.class.superclass, &block)
80
- ExpectationTarget.call(self.class.superclass, actual, block)
81
- end
284
+ example = ::Class.new(self) do
285
+ include ExpectationHelper::Its
82
286
 
83
- # Wraps the target of an expectation with the subject as actual value.
84
- #
85
- # @return [ExpectationTarget] (see #expect)
86
- def is_expected
87
- expect(subject)
287
+ define_method(:actual) do
288
+ subject.public_send(attribute, *args, **kwargs)
88
289
  end
290
+ end.new
89
291
 
90
- # Output a message to the console.
91
- #
92
- # @param message [String] The string to be notified about.
93
- #
94
- # @return [nil] Write a message to STDOUT.
95
- def log(message)
96
- Log.result(message)
97
- end
292
+ example.instance_eval(&block)
293
+ rescue ::SystemExit
294
+ Console.source(*block.source_location)
98
295
 
99
- # Indicate that an example is disabled pending some action.
100
- #
101
- # @param description [String] The reason why the example is pending.
102
- #
103
- # @return [nil] Write a message to STDOUT.
104
- def pending(description)
105
- Pending.result(description)
106
- end
107
- end
296
+ exit false
297
+ ensure
298
+ example&.send(AFTER_METHOD)
108
299
  end
109
300
 
110
- # @private
301
+ # Defines a pending test case.
302
+ #
303
+ # `&block` is never evaluated. It can be used to describe behaviour that is
304
+ # not yet implemented.
305
+ #
306
+ # @example
307
+ # require "r_spec"
308
+ #
309
+ # RSpec.describe "an example" do
310
+ # pending "is implemented but waiting" do
311
+ # expect something to be finished
312
+ # end
111
313
  #
112
- # @return [String] A random constant name for a test class.
113
- def self.random_test_const_name
114
- "Test#{::SecureRandom.hex(4).to_i(16)}"
314
+ # pending "is not yet implemented and waiting"
315
+ # end
316
+ #
317
+ # # Output to the console
318
+ # # Warning: is implemented but waiting.
319
+ # # Warning: is not yet implemented and waiting.
320
+ #
321
+ # @param message [String] The reason why the example is pending.
322
+ #
323
+ # @return [nil] Write a message to STDOUT.
324
+ #
325
+ # @api public
326
+ def self.pending(message)
327
+ Console.passed_spec Error::PendingExpectation.result(message)
115
328
  end
116
329
 
117
- private_class_method :example, :random_test_const_name
330
+ private
331
+
332
+ def described_class
333
+ raise Error::UndefinedDescribedClass,
334
+ "the first argument to at least one example group must be a module"
335
+ end
336
+
337
+ def subject
338
+ raise Error::UndefinedSubject, "subject not explicitly defined"
339
+ end
340
+
341
+ define_method(AFTER_METHOD) do
342
+ # do nothing by default
343
+ end
118
344
  end
119
345
  end
120
-
121
- require_relative "expectation_target"
122
- require_relative "log"
123
- require_relative "pending"
124
- require_relative "test"