r_spec-clone 1.2.0 → 1.2.4

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: 0e923f4f11f83ba71f2c7267393691edf8bf8ea9be1ae3598f57cb637d71c411
4
- data.tar.gz: 18d34b05058e5f17eca7c874f03d52fdac54e995858e1531ee5acff77ceed552
3
+ metadata.gz: 2e5c67c5d900bbe71f8e647c1fd1c4e5bd6a96e01b9db3d228918dedb755edd2
4
+ data.tar.gz: 3907ef8dab4a2bb8c751eacf281c5f643b2f3a9c774fbb71ec1cfc0707072dea
5
5
  SHA512:
6
- metadata.gz: 80419c1141a6efc61b8f47294d08c8932a416b8c983419a63afdab4bbf8a593d865c37ecb37f84e186459004cd7f807d87d5f99e5625ded0bb8fa885b2ad50d0
7
- data.tar.gz: 7796a4e33df45136434543f1f2dc2a0ae32a8d93d61bf11515ab0b8d3df7b300b78bd4f5976c913b777ea75da9500b85389268c51f4851eaf30fbe6012aa25fc
6
+ metadata.gz: 758790442ee21c56a0d40f98385ad7bdfbcbc88d527fc266f86641623fed9c172b68144e538a4565dd19e211b53b3412d847813978991853bfe2d8ab82a67cd7
7
+ data.tar.gz: fd4c45945f0f84de6361f68aaec5d84feb6f9983f27248b42abb5757b9c17a3fd15b00e4c0ab7314a6050d6234f8e3b8e5ef997d87df26b34e7f199322dc621d
data/README.md CHANGED
@@ -6,6 +6,7 @@ A minimalist __RSpec clone__ with all the essentials.
6
6
 
7
7
  ## Status
8
8
 
9
+ [![Home](https://img.shields.io/badge/Home-r--spec.dev-00af8b)](https://r-spec.dev/)
9
10
  [![Version](https://img.shields.io/github/v/tag/cyril/r_spec-clone.rb?label=Version&logo=github)](https://github.com/cyril/r_spec-clone.rb/releases)
10
11
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/cyril/r_spec-clone.rb/main)
11
12
  [![CI](https://github.com/cyril/r_spec-clone.rb/workflows/CI/badge.svg?branch=main)](https://github.com/cyril/r_spec-clone.rb/actions?query=workflow%3Aci+branch%3Amain)
@@ -23,7 +24,7 @@ A minimalist __RSpec clone__ with all the essentials.
23
24
 
24
25
  * There is no option to activate monkey-patching.
25
26
  * It does not rely on [hacks such as `at_exit` hook](https://blog.arkency.com/2013/06/are-we-abusing-at-exit/) to trigger the tests.
26
- * Built-in matchers [do not trust _actual_](https://asciinema.org/a/29172?autoplay=1&speed=2) and do not send it messages.
27
+ * Malicious _actual values_ cannot [hack results](https://asciinema.org/a/423547?autoplay=1&speed=2).
27
28
  * If no `subject` has been explicitly determined, none is defined.
28
29
  * If no described class is set, `described_class` is undefined instead of `nil`.
29
30
  * Expectations cannot be added inside a `before` block.
@@ -67,7 +68,7 @@ A basic spec looks something like this:
67
68
 
68
69
  ### Anatomy of a spec file
69
70
 
70
- To use the `RSpec` module and its DSL, you need to add `require "r_spec/clone"` to your spec files.
71
+ To use the `RSpec` module and its DSL, you need to add `require "r_spec"` to your spec files.
71
72
  Many projects use a custom spec helper which organizes these includes.
72
73
 
73
74
  Concrete test cases are defined in `it` blocks.
@@ -95,12 +96,28 @@ For unit tests, it is recommended to follow the conventions for method names:
95
96
 
96
97
  To establish certain contexts — think _empty array_ versus _array with elements_ — the `context` method may be used to communicate this to the reader.
97
98
 
98
- Finally, each block of code can be run in a subprocess to isolate side effects with the equivalent methods:
99
+ To execute unit tests while isolating side effects in a sub-process, declined methods can be used: `describe!`, `context!`, `it!`, `its!`. Here is an example:
99
100
 
100
- * `describe!`
101
- * `context!`
102
- * `it!`
103
- * `its!`
101
+ ```ruby
102
+ app = "foo"
103
+
104
+ RSpec.describe "Side effects per example" do
105
+ it! "runs the example in isolation" do
106
+ expect { app.gsub!("foo", "bar") }.to eq "bar"
107
+ expect(app).to eq "bar"
108
+ end
109
+
110
+ it "runs the example" do
111
+ expect(app).to eq "foo"
112
+ end
113
+ end
114
+
115
+ # Success: expected to eq "bar".
116
+ # Success: expected to eq "bar".
117
+ # Success: expected to eq "foo".
118
+ ```
119
+
120
+ Note: if you are wondering what the Ruby code generated by using the DSL might look like, an article presents the correspondence between each method via simple examples, available in [English](https://dev.to/cyri_/what-ruby-code-to-expect-from-a-testing-dsl-4oe1), [Chinese](https://ruby-china.org/topics/41441) and [Japanese](https://qiita.com/cyril/items/17ee758e162bae144a07).
104
121
 
105
122
  ### Expectations
106
123
 
@@ -221,13 +238,13 @@ bundle exec rake
221
238
 
222
239
  Benchmark against [100 executions of a file containing 1 expectation](https://github.com/cyril/r_spec-clone.rb/blob/main/benchmark/boot_time/) (lower is better).
223
240
 
224
- ![Boot time](https://r-spec.dev/benchmark-boot-time.svg)
241
+ ![Boot time benchmark](https://r-spec.dev/benchmark-boot-time.svg)
225
242
 
226
- ### Run time
243
+ ### Runtime
227
244
 
228
245
  Benchmark against [1 execution of a file containing 1,000,000 expectations](https://github.com/cyril/r_spec-clone.rb/blob/main/benchmark/run_time/) (lower is better).
229
246
 
230
- ![Run time](https://r-spec.dev/benchmark-run-time.svg)
247
+ ![Runtime benchmark](https://r-spec.dev/benchmark-run-time.svg)
231
248
 
232
249
  ## Test suite
233
250
 
@@ -237,6 +254,7 @@ __RSpec clone__'s specifications are self-described here: [spec/](https://github
237
254
 
238
255
  * Home page: [https://r-spec.dev/](https://r-spec.dev/)
239
256
  * Cheatsheet: [https://r-spec.dev/cheatsheet.html](https://r-spec.dev/cheatsheet.html)
257
+ * Blog post: [https://batman.buzz/introducing-a-new-rspec-850d48c0f901](https://batman.buzz/introducing-a-new-rspec-850d48c0f901)
240
258
  * Source code: [https://github.com/cyril/r_spec-clone.rb](https://github.com/cyril/r_spec-clone.rb)
241
259
  * API Doc: [https://rubydoc.info/gems/r_spec-clone](https://rubydoc.info/gems/r_spec-clone)
242
260
  * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
data/lib/r_spec.rb CHANGED
@@ -5,7 +5,7 @@ require_relative File.join("r_spec", "clone", "dsl")
5
5
  # Top level namespace for the RSpec clone.
6
6
  #
7
7
  # @example The true from the false
8
- # require "r_spec/clone"
8
+ # require "r_spec"
9
9
  #
10
10
  # RSpec.describe "The true from the false" do
11
11
  # it { expect(false).not_to be true }
@@ -15,7 +15,7 @@ require_relative File.join("r_spec", "clone", "dsl")
15
15
  # # Success: expected false not to be true.
16
16
  #
17
17
  # @example The basic behavior of arrays
18
- # require "r_spec/clone"
18
+ # require "r_spec"
19
19
  #
20
20
  # RSpec.describe Array do
21
21
  # describe "#size" do
@@ -41,7 +41,7 @@ require_relative File.join("r_spec", "clone", "dsl")
41
41
  # # Success: expected false to be false.
42
42
  #
43
43
  # @example An inherited definition of let
44
- # require "r_spec/clone"
44
+ # require "r_spec"
45
45
  #
46
46
  # RSpec.describe Integer do
47
47
  # let(:answer) { 42 }
@@ -69,7 +69,7 @@ module RSpec
69
69
  # array_ versus _array with elements_.
70
70
  #
71
71
  # @example
72
- # require "r_spec/clone"
72
+ # require "r_spec"
73
73
  #
74
74
  # RSpec.context "when divided by zero" do
75
75
  # subject { 42 / 0 }
@@ -83,30 +83,62 @@ module RSpec
83
83
  # @param description [String] A description that usually begins with "when",
84
84
  # "with" or "without".
85
85
  # @param block [Proc] The block to define the specs.
86
- #
87
- # @api public
88
86
  def self.context(description, &block)
89
87
  Clone::Dsl.context(description, &block)
90
88
  end
91
89
 
92
90
  # :nocov:
93
- #
91
+
94
92
  # Runs a context example group in a subprocess to isolate side effects.
95
93
  #
94
+ # @example
95
+ # str = "Hello, world!"
96
+ #
97
+ # require "r_spec"
98
+ #
99
+ # RSpec.context! "when a string becomes uppercase" do
100
+ # before do
101
+ # str.upcase!
102
+ # end
103
+ #
104
+ # it { expect(str).to eq "HELLO, WORLD!" }
105
+ # end
106
+ #
107
+ # # Output to the console
108
+ # # Success: expected to eq "HELLO, WORLD!".
109
+ #
110
+ # RSpec.it { expect(str).to eq "Hello, world!" }
111
+ #
112
+ # # Output to the console
113
+ # # Success: expected to eq "Hello, world!".
114
+ #
96
115
  # @param (see #context)
97
116
  def self.context!(description, &block)
98
117
  Clone::Dsl.context!(description, &block)
99
118
  end
119
+
100
120
  # :nocov:
101
121
 
102
122
  # Defines an example group that describes a unit to be tested.
103
123
  #
104
124
  # @example
105
- # require "r_spec/clone"
125
+ # require "r_spec"
126
+ #
127
+ # RSpec.describe String do
128
+ # it { expect(described_class).to be String }
129
+ # end
130
+ #
131
+ # # Output to the console
132
+ # # Success: expected to be String.
133
+ #
134
+ # @example
135
+ # require "r_spec"
106
136
  #
107
137
  # RSpec.describe String do
138
+ # let(:foo) { "foo" }
139
+ #
108
140
  # describe "+" do
109
- # it("concats") { expect("foo" + "bar").to eq "foobar" }
141
+ # it("concats") { expect(foo + "bar").to eq "foobar" }
110
142
  # end
111
143
  # end
112
144
  #
@@ -115,20 +147,40 @@ module RSpec
115
147
  #
116
148
  # @param const [Module, String] A module to include in block context.
117
149
  # @param block [Proc] The block to define the specs.
118
- #
119
- # @api public
120
150
  def self.describe(const, &block)
121
151
  Clone::Dsl.describe(const, &block)
122
152
  end
123
153
 
124
154
  # :nocov:
125
- #
155
+
126
156
  # Runs a describe example group in a subprocess to isolate side effects.
127
157
  #
158
+ # @example
159
+ # $app = "foo"
160
+ #
161
+ # require "r_spec"
162
+ #
163
+ # RSpec.describe! "#gsub!" do
164
+ # before do
165
+ # $app.gsub!("o", "0")
166
+ # end
167
+ #
168
+ # it { expect($app).to eq "f00" }
169
+ # end
170
+ #
171
+ # # Output to the console
172
+ # # Success: expected to eq "f00".
173
+ #
174
+ # RSpec.it { expect($app).to eq "foo" }
175
+ #
176
+ # # Output to the console
177
+ # # Success: expected to eq "foo".
178
+ #
128
179
  # @param (see #describe)
129
180
  def self.describe!(const, &block)
130
181
  Clone::Dsl.describe!(const, &block)
131
182
  end
183
+
132
184
  # :nocov:
133
185
 
134
186
  # Defines a concrete test case.
@@ -136,7 +188,7 @@ module RSpec
136
188
  # The test is performed by the block supplied to &block.
137
189
  #
138
190
  # @example The integer after 41
139
- # require "r_spec/clone"
191
+ # require "r_spec"
140
192
  #
141
193
  # RSpec.it { expect(41.next).to be 42 }
142
194
  #
@@ -149,21 +201,21 @@ module RSpec
149
201
  # @param name [String, nil] The name of the spec.
150
202
  # @param block [Proc] An expectation to evaluate.
151
203
  #
152
- # @raise (see RSpec::ExpectationTarget::Base#result)
153
- # @return (see RSpec::ExpectationTarget::Base#result)
154
- #
155
- # @api public
204
+ # @raise (see RSpec::Clone::ExpectationTarget::Base#result)
205
+ # @return (see RSpec::Clone::ExpectationTarget::Base#result)
156
206
  def self.it(name = nil, &block)
157
207
  Clone::Dsl.it(name, &block)
158
208
  end
159
209
 
160
210
  # :nocov:
161
- #
211
+
162
212
  # Runs a concrete test case in a subprocess to isolate side effects.
163
213
  #
164
214
  # @example
165
215
  # app = "Hello, world!"
166
216
  #
217
+ # require "r_spec"
218
+ #
167
219
  # RSpec.it! { expect(app.gsub!("world", "Alice")).to eq "Hello, Alice!" }
168
220
  #
169
221
  # # Output to the console
@@ -181,6 +233,7 @@ module RSpec
181
233
  def self.it!(name = nil, &block)
182
234
  Clone::Dsl.it!(name, &block)
183
235
  end
236
+
184
237
  # :nocov:
185
238
 
186
239
  # Defines a pending test case.
@@ -189,7 +242,7 @@ module RSpec
189
242
  # not yet implemented.
190
243
  #
191
244
  # @example
192
- # require "r_spec/clone"
245
+ # require "r_spec"
193
246
  #
194
247
  # RSpec.pending "is implemented but waiting" do
195
248
  # expect something to be finished
@@ -207,8 +260,6 @@ module RSpec
207
260
  # @param message [String] The reason why the example is pending.
208
261
  #
209
262
  # @return [nil] Write a message to STDOUT.
210
- #
211
- # @api public
212
263
  def self.pending(message)
213
264
  Clone::Dsl.pending(message)
214
265
  end
@@ -3,8 +3,6 @@
3
3
  module RSpec
4
4
  module Clone
5
5
  # Send log messages to the console.
6
- #
7
- # @api private
8
6
  module Console
9
7
  # @param report [::Expresenter::Pass] Passed expectation result presenter.
10
8
  #
@@ -16,7 +16,7 @@ module RSpec
16
16
  # Executes the given block before each spec in the current context runs.
17
17
  #
18
18
  # @example
19
- # require "r_spec/clone"
19
+ # require "r_spec"
20
20
  #
21
21
  # RSpec.describe Integer do
22
22
  # before do
@@ -42,6 +42,8 @@ module RSpec
42
42
  # # Success: expected to be 123.
43
43
  #
44
44
  # @param block [Proc] The content to execute at the class initialization.
45
+ #
46
+ # @api public
45
47
  def self.before(&block)
46
48
  define_method(BEFORE_METHOD) do
47
49
  super()
@@ -54,7 +56,7 @@ module RSpec
54
56
  # Executes the given block after each spec in the current context runs.
55
57
  #
56
58
  # @example
57
- # require "r_spec/clone"
59
+ # require "r_spec"
58
60
  #
59
61
  # RSpec.describe Integer do
60
62
  # after do
@@ -69,6 +71,8 @@ module RSpec
69
71
  # # That is the answer to everything.
70
72
  #
71
73
  # @param block [Proc] The content to execute at the class initialization.
74
+ #
75
+ # @api public
72
76
  def self.after(&block)
73
77
  define_method(AFTER_METHOD) do
74
78
  instance_exec(&block)
@@ -81,7 +85,7 @@ module RSpec
81
85
  # Sets a user-defined property.
82
86
  #
83
87
  # @example
84
- # require "r_spec/clone"
88
+ # require "r_spec"
85
89
  #
86
90
  # RSpec.describe "Name stories" do
87
91
  # let(:name) { "Bob" }
@@ -103,6 +107,8 @@ module RSpec
103
107
  # @param block [Proc] The content of the method to define.
104
108
  #
105
109
  # @return [Symbol] A private method that define the block content.
110
+ #
111
+ # @api public
106
112
  def self.let(name, *args, **kwargs, &block)
107
113
  raise Error::ReservedMethod if [BEFORE_METHOD, AFTER_METHOD].include?(name.to_sym)
108
114
 
@@ -112,7 +118,7 @@ module RSpec
112
118
  # Sets a user-defined property named {#subject}.
113
119
  #
114
120
  # @example
115
- # require "r_spec/clone"
121
+ # require "r_spec"
116
122
  #
117
123
  # RSpec.describe Array do
118
124
  # subject { [1, 2, 3] }
@@ -120,13 +126,23 @@ module RSpec
120
126
  # it "has the prescribed elements" do
121
127
  # expect(subject).to eq([1, 2, 3])
122
128
  # end
129
+ #
130
+ # it { is_expected.to be_an_instance_of described_class }
131
+ #
132
+ # its(:size) { is_expected.to be 3 }
133
+ # its(:downcase) { is_expected.to raise_exception NoMethodError }
123
134
  # end
124
135
  #
125
136
  # # Output to the console
126
137
  # # Success: expected to eq [1, 2, 3].
138
+ # # Success: expected [1, 2, 3] to be an instance of Array.
139
+ # # Success: expected to be 3.
140
+ # # Success: undefined method `downcase' for [1, 2, 3]:Array.
127
141
  #
128
142
  # @param block [Proc] The subject to set.
129
143
  # @return [Symbol] A {#subject} method that define the block content.
144
+ #
145
+ # @api public
130
146
  def self.subject(&block)
131
147
  let(__method__, &block)
132
148
  end
@@ -134,7 +150,7 @@ module RSpec
134
150
  # Defines an example group that describes a unit to be tested.
135
151
  #
136
152
  # @example
137
- # require "r_spec/clone"
153
+ # require "r_spec"
138
154
  #
139
155
  # RSpec.describe String do
140
156
  # describe "+" do
@@ -147,6 +163,8 @@ module RSpec
147
163
  #
148
164
  # @param const [Module, String] A module to include in block context.
149
165
  # @param block [Proc] The block to define the specs.
166
+ #
167
+ # @api public
150
168
  def self.describe(const, &block)
151
169
  desc = ::Class.new(self)
152
170
  desc.let(:described_class) { const } if const.is_a?(::Module)
@@ -154,20 +172,53 @@ module RSpec
154
172
  end
155
173
 
156
174
  # :nocov:
157
- #
175
+
158
176
  # Runs a describe example group in a subprocess to isolate side effects.
159
177
  #
178
+ # @example
179
+ # $app = "foo"
180
+ #
181
+ # require "r_spec"
182
+ #
183
+ # RSpec.describe "Scoped side effects" do
184
+ # describe! "#gsub!" do
185
+ # before do
186
+ # $app.gsub!("o", "0")
187
+ # end
188
+ #
189
+ # context! "when isolated in the context" do
190
+ # before do
191
+ # $app.gsub!("f", "F")
192
+ # end
193
+ #
194
+ # it { expect($app).to eq "F00" }
195
+ # end
196
+ #
197
+ # it { expect($app).to eq "f00" }
198
+ # end
199
+ #
200
+ # it { expect($app).to eq "foo" }
201
+ # end
202
+ #
203
+ # # Output to the console
204
+ # # Success: expected to eq "F00".
205
+ # # Success: expected to eq "f00".
206
+ # # Success: expected to eq "foo".
207
+ #
160
208
  # @param (see #describe)
209
+ #
210
+ # @api public
161
211
  def self.describe!(const, &block)
162
212
  fork! { describe(const, &block) }
163
213
  end
214
+
164
215
  # :nocov:
165
216
 
166
217
  # Defines an example group that establishes a specific context, like
167
218
  # _empty array_ versus _array with elements_.
168
219
  #
169
220
  # @example
170
- # require "r_spec/clone"
221
+ # require "r_spec"
171
222
  #
172
223
  # RSpec.describe "web resource" do
173
224
  # context "when resource is not found" do
@@ -186,19 +237,55 @@ module RSpec
186
237
  # @param _description [String] A description that usually begins with
187
238
  # "when", "with" or "without".
188
239
  # @param block [Proc] The block to define the specs.
240
+ #
241
+ # @api public
189
242
  def self.context(_description, &block)
190
243
  desc = ::Class.new(self)
191
244
  desc.instance_eval(&block)
192
245
  end
193
246
 
194
247
  # :nocov:
195
- #
248
+
196
249
  # Runs a context example group in a subprocess to isolate side effects.
197
250
  #
251
+ # @example
252
+ # app = "Hello, world!"
253
+ #
254
+ # require "r_spec"
255
+ #
256
+ # RSpec.describe String do
257
+ # subject do
258
+ # app
259
+ # end
260
+ #
261
+ # before do
262
+ # subject.gsub!("world", person)
263
+ # end
264
+ #
265
+ # context! "when Alice is greeted" do
266
+ # let(:person) { "Alice" }
267
+ #
268
+ # it { is_expected.to eq "Hello, Alice!" }
269
+ # end
270
+ #
271
+ # context! "when Bob is greeted" do
272
+ # let(:person) { "Bob" }
273
+ #
274
+ # it { is_expected.to eq "Hello, Bob!" }
275
+ # end
276
+ # end
277
+ #
278
+ # # Output to the console
279
+ # # Success: expected to eq "Hello, Alice!".
280
+ # # Success: expected to eq "Hello, Bob!".
281
+ #
198
282
  # @param (see #context)
283
+ #
284
+ # @api public
199
285
  def self.context!(description, &block)
200
286
  fork! { context(description, &block) }
201
287
  end
288
+
202
289
  # :nocov:
203
290
 
204
291
  # Defines a concrete test case.
@@ -206,7 +293,7 @@ module RSpec
206
293
  # The test is performed by the block supplied to `&block`.
207
294
  #
208
295
  # @example The integer after 41
209
- # require "r_spec/clone"
296
+ # require "r_spec"
210
297
  #
211
298
  # RSpec.describe Integer do
212
299
  # it { expect(41.next).to be 42 }
@@ -216,7 +303,7 @@ module RSpec
216
303
  # # Success: expected to be 42.
217
304
  #
218
305
  # @example A division by zero
219
- # require "r_spec/clone"
306
+ # require "r_spec"
220
307
  #
221
308
  # RSpec.describe Integer do
222
309
  # subject { 41 }
@@ -239,29 +326,54 @@ module RSpec
239
326
  #
240
327
  # @raise (see ExpectationTarget::Base#result)
241
328
  # @return (see ExpectationTarget::Base#result)
329
+ #
330
+ # @api public
242
331
  def self.it(_name = nil, &block)
243
- example = ::Class.new(self) { include ExpectationHelper::It }.new
244
- run(example, &block)
332
+ run(example_without_attribute.new, &block)
245
333
  end
246
334
 
247
335
  # :nocov:
248
- #
336
+
249
337
  # Runs a concrete test case in a subprocess to isolate side effects.
250
338
  #
339
+ # @example
340
+ # app = "foo"
341
+ #
342
+ # require "r_spec"
343
+ #
344
+ # RSpec.describe "Side effects per example" do
345
+ # it! "runs the example in isolation" do
346
+ # expect { app.gsub!("foo", "bar") }.to eq "bar"
347
+ # expect(app).to eq "bar"
348
+ # end
349
+ #
350
+ # it "runs the example" do
351
+ # expect(app).to eq "foo"
352
+ # end
353
+ # end
354
+ #
355
+ # # Output to the console
356
+ # # Success: expected to eq "bar".
357
+ # # Success: expected to eq "bar".
358
+ # # Success: expected to eq "foo".
359
+ #
251
360
  # @param (see #it)
252
361
  #
253
362
  # @raise (see ExpectationTarget::Base#result)
254
363
  # @return (see ExpectationTarget::Base#result)
364
+ #
365
+ # @api public
255
366
  def self.it!(name = nil, &block)
256
367
  fork! { it(name, &block) }
257
368
  end
369
+
258
370
  # :nocov:
259
371
 
260
372
  # Defines a single concrete test case that specifies the actual value of
261
373
  # an attribute of the subject using {ExpectationHelper::Its#is_expected}.
262
374
  #
263
375
  # @example The integer after 41
264
- # require "r_spec/clone"
376
+ # require "r_spec"
265
377
  #
266
378
  # RSpec.describe Integer do
267
379
  # subject { 41 }
@@ -273,7 +385,7 @@ module RSpec
273
385
  # # Success: expected to be 42.
274
386
  #
275
387
  # @example A division by zero
276
- # require "r_spec/clone"
388
+ # require "r_spec"
277
389
  #
278
390
  # RSpec.describe Integer do
279
391
  # subject { 41 }
@@ -285,10 +397,10 @@ module RSpec
285
397
  # # Success: divided by 0.
286
398
  #
287
399
  # @example A spec without subject
288
- # require "r_spec/clone"
400
+ # require "r_spec"
289
401
  #
290
402
  # RSpec.describe Integer do
291
- # its(:boom) { is_expected.to raise_exception RSpec::Clone::Error::UndefinedSubject }
403
+ # its(:abs) { is_expected.to raise_exception RSpec::Clone::Error::UndefinedSubject }
292
404
  # end
293
405
  #
294
406
  # # Output to the console
@@ -301,30 +413,48 @@ module RSpec
301
413
  #
302
414
  # @raise (see ExpectationTarget::Base#result)
303
415
  # @return (see ExpectationTarget::Base#result)
416
+ #
417
+ # @api public
304
418
  def self.its(attribute, *args, **kwargs, &block)
305
- example = ::Class.new(self) do
306
- include ExpectationHelper::Its
307
-
308
- define_method(:actual) do
309
- subject.public_send(attribute, *args, **kwargs)
310
- end
311
- end.new
312
-
313
- run(example, &block)
419
+ run(example_with_attribute(attribute, *args, **kwargs).new, &block)
314
420
  end
315
421
 
316
422
  # :nocov:
317
- #
423
+
318
424
  # Runs a single concrete test case in a subprocess to isolate side
319
425
  # effects.
320
426
  #
427
+ # @example
428
+ # app = "foo"
429
+ #
430
+ # require "r_spec"
431
+ #
432
+ # RSpec.describe "Isolated side effect" do
433
+ # subject do
434
+ # app
435
+ # end
436
+ #
437
+ # its!(:upcase) { is_expected.to eq "FOO" }
438
+ #
439
+ # it "tests the original value" do
440
+ # expect(app).to eq "foo"
441
+ # end
442
+ # end
443
+ #
444
+ # # Output to the console
445
+ # # Success: expected to eq "FOO".
446
+ # # Success: expected to eq "foo".
447
+ #
321
448
  # @param (see #it)
322
449
  #
323
450
  # @raise (see ExpectationTarget::Base#result)
324
451
  # @return (see ExpectationTarget::Base#result)
452
+ #
453
+ # @api public
325
454
  def self.its!(attribute, *args, **kwargs, &block)
326
455
  fork! { its(attribute, *args, **kwargs, &block) }
327
456
  end
457
+
328
458
  # :nocov:
329
459
 
330
460
  # Defines a pending test case.
@@ -333,7 +463,7 @@ module RSpec
333
463
  # is not yet implemented.
334
464
  #
335
465
  # @example
336
- # require "r_spec/clone"
466
+ # require "r_spec"
337
467
  #
338
468
  # RSpec.describe "an example" do
339
469
  # pending "is implemented but waiting" do
@@ -356,6 +486,25 @@ module RSpec
356
486
  Console.passed_spec Error::PendingExpectation.result(message)
357
487
  end
358
488
 
489
+ # Example class for concrete test case.
490
+ def self.example_without_attribute
491
+ ::Class.new(self) do
492
+ prepend ExpectationHelper::It
493
+ end
494
+ end
495
+
496
+ # Example class for concrete test case that specifies the actual value of
497
+ # an attribute of the subject.
498
+ def self.example_with_attribute(attribute, *args, **kwargs)
499
+ ::Class.new(self) do
500
+ prepend ExpectationHelper::Its
501
+
502
+ define_method(:actual) do
503
+ subject.public_send(attribute, *args, **kwargs)
504
+ end
505
+ end
506
+ end
507
+
359
508
  # Creates a subprocess and runs the block inside.
360
509
  def self.fork!(&block)
361
510
  pid = fork(&block)
@@ -375,15 +524,18 @@ module RSpec
375
524
  example&.send(AFTER_METHOD)
376
525
  end
377
526
 
378
- private_class_method :fork!, :run
527
+ private_class_method :example_without_attribute, :example_with_attribute, :fork!, :run
379
528
 
380
529
  private
381
530
 
531
+ # If the first argument to a {.describe} definition is a class (or a
532
+ # module), this method will be overridden to return it.
382
533
  def described_class
383
534
  raise Error::UndefinedDescribedClass,
384
535
  "the first argument to at least one example group must be a module"
385
536
  end
386
537
 
538
+ # If a subject is defined, this method will be overridden to return it.
387
539
  def subject
388
540
  raise Error::UndefinedSubject, "subject not explicitly defined"
389
541
  end
@@ -8,8 +8,6 @@ require_relative File.join("error", "undefined_subject")
8
8
  module RSpec
9
9
  module Clone
10
10
  # Namespace for exceptions.
11
- #
12
- # @api private
13
11
  module Error
14
12
  end
15
13
  end
@@ -6,8 +6,6 @@ module RSpec
6
6
  module Clone
7
7
  module Error
8
8
  # Exception for pending expectations.
9
- #
10
- # @api private
11
9
  class PendingExpectation < ::RuntimeError
12
10
  # @param message [String] The not implemented expectation description.
13
11
  #
@@ -4,8 +4,6 @@ module RSpec
4
4
  module Clone
5
5
  module Error
6
6
  # Exception for reserved methods.
7
- #
8
- # @api private
9
7
  class ReservedMethod < ::RuntimeError
10
8
  end
11
9
  end
@@ -4,8 +4,6 @@ module RSpec
4
4
  module Clone
5
5
  module Error
6
6
  # Exception for undefined described classes.
7
- #
8
- # @api private
9
7
  class UndefinedDescribedClass < ::RuntimeError
10
8
  end
11
9
  end
@@ -4,8 +4,6 @@ module RSpec
4
4
  module Clone
5
5
  module Error
6
6
  # Exception for undefined subjects.
7
- #
8
- # @api private
9
7
  class UndefinedSubject < ::RuntimeError
10
8
  end
11
9
  end
@@ -18,8 +18,8 @@ module RSpec
18
18
  # @return [Block, Value] The wrapped target of an expectation.
19
19
  #
20
20
  # @example
21
- # expect("foo") # => #<RSpec::ExpectationTarget::Value:0x00007fb6b823 @actual="foo">
22
- # expect { Boom } # => #<RSpec::ExpectationTarget::Block:0x00007fb6b826 @callable=#<Proc:0x00007fb6b826>>
21
+ # expect("foo") # => #<RSpec::Clone::ExpectationTarget::Value:0x00007f @actual="foo">
22
+ # expect { Boom } # => #<RSpec::Clone::ExpectationTarget::Block:0x00007f @callable=#<Proc:0x00007f>>
23
23
  #
24
24
  # @api public
25
25
  def expect(value = self.class.superclass, &block)
@@ -31,7 +31,7 @@ module RSpec
31
31
  # @return [Block] The wrapped target of an expectation.
32
32
  #
33
33
  # @example
34
- # is_expected # => #<RSpec::ExpectationTarget::Block:0x00007fb6b8263df8 @callable=#<Proc:0x00007fb6b8263e20>>
34
+ # is_expected # => #<RSpec::Clone::ExpectationTarget::Block:0x00007fb6b8 @callable=#<Proc:0x00007fb6b8>>
35
35
  #
36
36
  # @api public
37
37
  def is_expected
@@ -15,7 +15,7 @@ module RSpec
15
15
  # @return [Block] The wrapped target of an expectation.
16
16
  #
17
17
  # @example
18
- # is_expected # => #<RSpec::ExpectationTarget::Block:0x00007fb6b8263df8 @callable=#<Proc:0x00007fb6b8263e20>>
18
+ # is_expected # => #<RSpec::Clone::ExpectationTarget::Block:0x00007f @callable=#<Proc:0x00007f>>
19
19
  #
20
20
  # @api public
21
21
  def is_expected
@@ -1,16 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "matchi/rspec"
4
-
5
- require_relative File.join("..", "error", "pending_expectation")
4
+ require "matchi/helper"
6
5
 
7
6
  module RSpec
8
7
  module Clone
9
8
  module ExpectationHelper
10
9
  # Abstract expectation helper base module.
11
10
  #
12
- # This module defines a number of methods to create expectations, which are
13
- # automatically included into examples.
11
+ # This module defines a number of methods to create expectations, which
12
+ # are automatically included into examples.
14
13
  #
15
14
  # It also includes a collection of expectation matchers 🤹
16
15
  #
@@ -6,12 +6,10 @@ require_relative File.join("expectation_target", "value")
6
6
  module RSpec
7
7
  module Clone
8
8
  # Wraps the target of an expectation.
9
- #
10
- # @api private
11
9
  module ExpectationTarget
12
10
  # @param undefined_value A sentinel value to be able to tell when the user
13
- # did not pass an argument. We can't use `nil` for that because `nil` is a
14
- # valid value to pass.
11
+ # did not pass an argument. We can't use `nil` for that because `nil` is
12
+ # a valid value to pass.
15
13
  # @param value [#object_id, nil] An actual value.
16
14
  # @param block [#call, nil] A code to evaluate.
17
15
  #
@@ -10,14 +10,14 @@ module RSpec
10
10
  module ExpectationTarget
11
11
  # Abstract expectation target base class.
12
12
  #
13
- # @note `RSpec::ExpectationTarget::Base` is not intended to be instantiated
14
- # directly by users. Use `expect` instead.
13
+ # @note `RSpec::Clone::ExpectationTarget::Base` is not intended to be
14
+ # instantiated directly by users. Use `expect` instead.
15
15
  class Base
16
16
  # Instantiate a new expectation target.
17
17
  #
18
- # @param actual [#object_id] The actual value of the code to evaluate.
19
- def initialize(actual)
20
- @actual = actual
18
+ # @param input [#object_id, Proc] The code to evaluate.
19
+ def initialize(input)
20
+ @input = input
21
21
  end
22
22
 
23
23
  # Runs the given expectation, passing if `matcher` returns true.
@@ -52,6 +52,36 @@ module RSpec
52
52
 
53
53
  protected
54
54
 
55
+ # @param test [::TestTube::Base] The state of the experiment.
56
+ # @param matcher [#matches?] The matcher.
57
+ # @param negate [Boolean] The assertion is positive or negative.
58
+ #
59
+ # @return [nil] Write a message to STDOUT.
60
+ #
61
+ # @raise [SystemExit] Terminate execution immediately by calling
62
+ # `Kernel.exit(false)` with a failure message written to STDERR.
63
+ def absolute_requirement(test, matcher:, negate:)
64
+ result(
65
+ passed?(test),
66
+ actual: test.actual,
67
+ error: test.error,
68
+ got: test.got,
69
+ matcher: matcher,
70
+ negate: negate
71
+ )
72
+ end
73
+
74
+ # Code experiment result.
75
+ #
76
+ # @param test [::TestTube::Base] The state of the experiment.
77
+ #
78
+ # @see https://github.com/fixrb/test_tube
79
+ #
80
+ # @return [Boolean] The result of the test (passed or failed).
81
+ def passed?(test)
82
+ test.got.equal?(true)
83
+ end
84
+
55
85
  # @param passed [Boolean] The high expectation passed or failed.
56
86
  # @param actual [#object_id] The actual value.
57
87
  # @param error [Exception, nil] Any raised exception.
@@ -63,8 +93,6 @@ module RSpec
63
93
  #
64
94
  # @raise [SystemExit] Terminate execution immediately by calling
65
95
  # `Kernel.exit(false)` with a failure message written to STDERR.
66
- #
67
- # @api private
68
96
  def result(passed, actual:, error:, got:, matcher:, negate:)
69
97
  Console.passed_spec ::Expresenter.call(passed).with(
70
98
  actual: actual,
@@ -16,31 +16,20 @@ module RSpec
16
16
  # # with `not_to`
17
17
  # expect { actual }.not_to be(4)
18
18
  #
19
- # @note `RSpec::ExpectationTarget::Block` is not intended to be instantiated
20
- # directly by users. Use `expect` instead.
19
+ # @note `RSpec::Clone::ExpectationTarget::Block` is not intended to be
20
+ # instantiated directly by users. Use `expect` instead.
21
21
  class Block < Base
22
22
  protected
23
23
 
24
24
  # @param matcher [#matches?] The matcher.
25
25
  # @param negate [Boolean] The assertion is positive or negative.
26
26
  #
27
- # @return [nil] Write a message to STDOUT.
27
+ # @return (see Base#absolute_requirement)
28
28
  #
29
- # @raise [SystemExit] Terminate execution immediately by calling
30
- # `Kernel.exit(false)` with a failure message written to STDERR.
29
+ # @raise (see Base#absolute_requirement)
31
30
  def absolute_requirement(matcher:, negate:)
32
- experiment = ::TestTube.invoke(
33
- @actual,
34
- isolation: false,
35
- matcher: matcher,
36
- negate: negate
37
- )
38
-
39
- result(
40
- experiment.got.equal?(true),
41
- actual: experiment.actual,
42
- error: experiment.error,
43
- got: experiment.got,
31
+ super(
32
+ ::TestTube.invoke(isolate: false, matcher: matcher, negate: negate, &@input),
44
33
  matcher: matcher,
45
34
  negate: negate
46
35
  )
@@ -16,30 +16,20 @@ module RSpec
16
16
  # # with `not_to`
17
17
  # expect(actual).not_to be(4)
18
18
  #
19
- # @note `RSpec::ExpectationTarget::Value` is not intended to be instantiated
20
- # directly by users. Use `expect` instead.
19
+ # @note `RSpec::Clone::ExpectationTarget::Value` is not intended to be
20
+ # instantiated directly by users. Use `expect` instead.
21
21
  class Value < Base
22
22
  protected
23
23
 
24
24
  # @param matcher [#matches?] The matcher.
25
25
  # @param negate [Boolean] The assertion is positive or negative.
26
26
  #
27
- # @return [nil] Write a message to STDOUT.
27
+ # @return (see Base#absolute_requirement)
28
28
  #
29
- # @raise [SystemExit] Terminate execution immediately by calling
30
- # `Kernel.exit(false)` with a failure message written to STDERR.
29
+ # @raise (see Base#absolute_requirement)
31
30
  def absolute_requirement(matcher:, negate:)
32
- experiment = ::TestTube.pass(
33
- @actual,
34
- matcher: matcher,
35
- negate: negate
36
- )
37
-
38
- result(
39
- experiment.got.equal?(true),
40
- actual: experiment.actual,
41
- error: experiment.error,
42
- got: experiment.got,
31
+ super(
32
+ ::TestTube.pass(@input, matcher: matcher, negate: negate),
43
33
  matcher: matcher,
44
34
  negate: negate
45
35
  )
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: r_spec-clone
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-01 00:00:00.000000000 Z
11
+ date: 2021-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expresenter
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.1.2
33
+ version: 1.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.1.2
40
+ version: 1.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: test_tube
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.1.0
47
+ version: 2.1.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.1.0
54
+ version: 2.1.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement