r_spec 1.0.0.beta1 → 1.0.0.beta6

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: 4ffdec1b2b30ba9271129d9332872597dbc00204d92495e4cbfa1f3b00896d8f
4
- data.tar.gz: 59c6a96b46b865ea72ecd3b84f80189f8173550447bed8820e1156130ba02c67
3
+ metadata.gz: 6facf7764beddac81425ab44d80ed3b306f6c63c3532401bb658240654b06845
4
+ data.tar.gz: 944d53b22a1ffeeca3afe990aa99a613493f9ff1b3aad3ceaa43ae0b9a8ec019
5
5
  SHA512:
6
- metadata.gz: 60c275a1bbe617d37623707f45cd7d8cb0673354a7e1751873ecf08e84160f77030530a284b0d759cb32705599a64d5a0bb2287acd10c8dbb3bd0f8b0a1328fa
7
- data.tar.gz: 19f8fa7e5727b596a11d2865fedf26953198d09604cdc2150ef037e88d78913305c5a257d4289e270a3697f4555524973df46ea081da1502082c25b864704a4d
6
+ metadata.gz: c28b4fbd53cf2a368022418c2ccbbf31cf4a35abce141f0999285bff90b4c84d7f36209894dafcf5362464d01fad3dcb724d942ae5db5c1095387997b087964f
7
+ data.tar.gz: 4c7a23212c2ce90a67b04e9fe25dda7812c37902eba8f6febdb9e4e2dcd33633fa43642d6905f9bfffa6215b467eb88cbb921d596f0276fb788cc876bc63e291
data/README.md CHANGED
@@ -1,27 +1,33 @@
1
1
  # RSpec clone
2
2
 
3
- A minimalist [RSpec](https://github.com/rspec/rspec) clone with an emphasis on correctness and simplicity.
3
+ A minimalist __[RSpec](https://github.com/rspec/rspec) clone__ with all the essentials.
4
4
 
5
- ![What did you expect?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-expect.jpg)
5
+ ![What did you RSpec?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-rspec.jpg)
6
6
 
7
7
  ## Status
8
8
 
9
9
  [![Gem Version](https://badge.fury.io/rb/r_spec.svg)](https://badge.fury.io/rb/r_spec)
10
10
  [![Build Status](https://travis-ci.org/cyril/r_spec.rb.svg?branch=main)](https://travis-ci.org/cyril/r_spec.rb)
11
11
  [![Inline Docs](https://inch-ci.org/github/cyril/r_spec.rb.svg)](https://inch-ci.org/github/cyril/r_spec.rb)
12
+ [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)](https://rubydoc.info/gems/r_spec/frames)
12
13
 
13
14
  ## Goal
14
15
 
15
- This clone attempts to provide most of RSpec's DSL without magic power, so that its code could reasonably become less complex than the code of your application.
16
+ This clone attempts to provide most of RSpec's DSL to express expected outcomes of a code example without magic power.
16
17
 
17
18
  ## Some differences
18
19
 
19
20
  * Less features and an implementation with much less code complexity.
20
21
  * Spec files can also be executed directly with the `ruby` executable.
21
- * There cannot be more than one expectation per example.
22
22
  * There is no option to activate monkey-patching.
23
- * Does not rely on hacks such as `at_exit` hook to trigger the tests.
24
- * Built-in matchers do not trust _actual_ and do not send it any message.
23
+ * It does not rely on hacks such as `at_exit` hook to trigger the tests.
24
+ * Built-in matchers do not trust _actual_ and do not send it messages.
25
+ * If no `subject` has been explicitly determined, none is defined.
26
+ * If no described class is set, `described_class` is undefined instead of `nil`.
27
+ * Expectations cannot be added inside a `before` block.
28
+ * [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not exposed to examples.
29
+ * The `let` method defines a helper method rather than a memoized helper method.
30
+ * The one-liner `is_expected` syntax also works with block expectations.
25
31
 
26
32
  ## Important ⚠️
27
33
 
@@ -40,7 +46,7 @@ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-ge
40
46
  Add this line to your application's Gemfile:
41
47
 
42
48
  ```ruby
43
- gem "r_spec", ">= 1.0.0.beta1"
49
+ gem "r_spec", ">= 1.0.0.beta6"
44
50
  ```
45
51
 
46
52
  And then execute:
@@ -55,47 +61,194 @@ Or install it yourself as:
55
61
  gem install r_spec --pre
56
62
  ```
57
63
 
64
+ ## Overview
65
+
66
+ __RSpec clone__ provides a structure for writing executable examples of how your code should behave.
67
+
68
+ Inspired by [RSpec](https://rspec.info/), it includes a domain specific language (DSL) that allows you to write examples in a way similar to plain english.
69
+
70
+ A basic spec looks something like this:
71
+
72
+ [![Super DRY example](https://asciinema.org/a/418672.svg)](https://asciinema.org/a/418672?autoplay=1)
73
+
58
74
  ## Usage
59
75
 
60
- Given this `greeting_spec.rb` spec:
76
+ ### Anatomy of a spec file
77
+
78
+ To use the `RSpec` module and its DSL, you need to add `require "r_spec"` to your spec files.
79
+ Many projects use a custom spec helper which organizes these includes.
80
+
81
+ Concrete test cases are defined in `it` blocks.
82
+ An optional descriptive string states it's purpose and a block contains the main logic performing the test.
83
+
84
+ Test cases that have been defined or outlined but are not yet expected to work can be defined using `pending` instead of `it`. They will not be run but show up in the spec report as pending.
85
+
86
+ An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
87
+ Each example can contain multiple expectations, but it should test only one specific behaviour.
88
+
89
+ To express an expectation, wrap an object or block in `expect`, call `to` (or `not_to`) and pass it a matcher object.
90
+ If the expectation is met, code execution continues.
91
+ Otherwise the example has _failed_ and other code will not be executed.
92
+
93
+ In test files, specs are structured by example groups which are defined by `describe` and `context` sections.
94
+ Typically a top level `describe` defines the outer unit (such as a class) to be tested by the spec.
95
+ Further `describe` sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
96
+
97
+ For unit tests, it is recommended to follow the conventions for method names:
98
+
99
+ * outer `describe` is the name of the class, inner `describe` targets methods;
100
+ * instance methods are prefixed with `#`, class methods with `.`.
101
+
102
+ To establish certain contexts — think _empty array_ versus _array with elements_ — the `context` method may be used to communicate this to the reader.
103
+ It has a different name, but behaves exactly like `describe`.
104
+
105
+ `describe` and `context` take an optional description as argument and a block containing the individual specs or nested groupings.
106
+
107
+ ### Expectations
108
+
109
+ Expectations define if the value being tested (_actual_) matches a certain value or specific criteria.
110
+
111
+ #### Equivalence
61
112
 
62
113
  ```ruby
63
- require "r_spec"
114
+ expect(actual).to eql(expected) # passes if expected.eql?(actual)
115
+ expect(actual).to eq(expected) # passes if expected.eql?(actual)
116
+ ```
64
117
 
65
- greeting = "Hello, world!"
118
+ #### Identity
66
119
 
67
- RSpec.describe String do
68
- context "Alice" do
69
- before { greeting.gsub!("world", "Alice") }
70
- it { expect(greeting).to eql "Hello, Alice!" }
71
- end
120
+ ```ruby
121
+ expect(actual).to equal(expected) # passes if expected.equal?(actual)
122
+ expect(actual).to be(expected) # passes if expected.equal?(actual)
123
+ ```
72
124
 
73
- context "Bob" do
74
- before { greeting.gsub!("world", "Bob") }
75
- it { expect(greeting).to eql "Hello, Bob!" }
76
- end
77
- end
125
+ #### Regular expressions
126
+
127
+ ```ruby
128
+ expect(actual).to match(expected) # passes if expected.match?(actual)
129
+ ```
130
+
131
+ #### Expecting errors
132
+
133
+ ```ruby
134
+ expect { actual }.to raise_exception(expected) # passes if expected exception is raised
78
135
  ```
79
136
 
80
- It can be tested in the console with the command:
137
+ #### Truth
138
+
139
+ ```ruby
140
+ expect(actual).to be_true # passes if true.equal?(actual)
141
+ ```
142
+
143
+ #### Untruth
144
+
145
+ ```ruby
146
+ expect(actual).to be_false # passes if false.equal?(actual)
147
+ ```
148
+
149
+ #### Nil
150
+
151
+ ```ruby
152
+ expect(actual).to be_nil # passes if nil.equal?(actual)
153
+ ```
154
+
155
+ #### Type/class
156
+
157
+ ```ruby
158
+ expect(actual).to be_instance_of(expected) # passes if expected.equal?(actual.class)
159
+ expect(actual).to be_an_instance_of(expected) # passes if expected.equal?(actual.class)
160
+ ```
161
+
162
+ ### Running specs
163
+
164
+ By convention, specs live in the `spec/` directory of a project. Spec files should end with `_spec.rb` to be recognizable as such.
165
+
166
+ Depending of the project settings, you may run the specs of a project by running `rake spec` (see [`rake` integration example](#rake-integration-example) below).
167
+ A single file can also be executed directly with the Ruby interpreter.
168
+
169
+ #### Examples
170
+
171
+ Run all specs in files matching `spec/**/*_spec.rb`:
81
172
 
82
173
  ```sh
83
- ruby greeting_spec.rb
174
+ bundle exec rake spec
175
+ ```
176
+
177
+ Run a single file:
178
+
179
+ ```sh
180
+ ruby spec/my/test/file_spec.rb
181
+ ```
182
+
183
+ I know that sounds weird, but the [`rspec` command line](https://relishapp.com/rspec/rspec-core/docs/command-line) is also working pretty well:
184
+
185
+ ```sh
186
+ rspec spec/my/test/file_spec.rb
187
+ rspec spec/my/test/file_spec.rb:42
188
+ rspec spec/my/test/
189
+ rspec
190
+ ```
191
+
192
+ ### Spec helper
193
+
194
+ Many projects use a custom spec helper file, usually named `spec/spec_helper.rb`.
195
+
196
+ This file is used to require `r_spec` and other includes, like the code from the project needed for every spec file.
197
+
198
+ ### `rake` integration example
199
+
200
+ The following `Rakefile` settings should be enough:
201
+
202
+ ```ruby
203
+ require "bundler/gem_tasks"
204
+ require "rake/testtask"
205
+
206
+ Rake::TestTask.new do |t|
207
+ t.pattern = "spec/**/*_spec.rb"
208
+ t.verbose = true
209
+ t.warning = true
210
+ end
211
+
212
+ task spec: :test
213
+ task default: :test
84
214
  ```
85
215
 
86
- > ..
87
- >
88
- > Ran 2 tests in 0.010994 seconds
89
- > 100% compliant - 0 infos, 0 failures, 0 errors
216
+ ## Performance
217
+
218
+ Benchmark against an single expectation executed 100 times from the console.
219
+
220
+ | Framework | Seconds to complete |
221
+ |-------------|---------------------|
222
+ | RSpec clone | [13.2](https://github.com/cyril/r_spec.rb/blob/main/benchmark/r_spec.rb) |
223
+ | RSpec | [32.7](https://github.com/cyril/r_spec.rb/blob/main/benchmark/rspec.rb) |
224
+ | minitest | [17.6](https://github.com/cyril/r_spec.rb/blob/main/benchmark/minitest.rb) |
225
+
226
+ ## Test suite
227
+
228
+ __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
90
229
 
91
230
  ## Contact
92
231
 
93
- * Home page: https://r-spec.dev/
232
+ * Home page: https://r-spec.dev
94
233
  * Source code: https://github.com/cyril/r_spec.rb
234
+ * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
235
+
236
+ ## Special thanks ❤️
237
+
238
+ I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
239
+ It's a great framework and it's a pleasure to work with every day.
240
+
241
+ Without RSpec, this clone would not have been possible.
242
+
243
+ ## Buy me a coffee ☕
244
+
245
+ If you like this project please consider making a small donation.
246
+
247
+ [![Donate with Ethereum](https://github.com/cyril/r_spec.rb/raw/main/img/donate-eth.svg)](https://etherscan.io/address/0x834b5c1feaff5aebf9cd0f25dc38e741d65ab773)
95
248
 
96
249
  ## Versioning
97
250
 
98
- __R_Spec__ follows [Semantic Versioning 2.0](https://semver.org/).
251
+ __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
99
252
 
100
253
  ## License
101
254
 
data/lib/r_spec.rb CHANGED
@@ -1,16 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Namespace for the RSpec framework.
3
+ require_relative File.join("r_spec", "dsl")
4
+
5
+ # Top level namespace for the RSpec clone.
4
6
  #
5
- # @api public
7
+ # @example The true from the false
8
+ # require "r_spec"
9
+ #
10
+ # RSpec.describe do
11
+ # it { expect(false).not_to be true }
12
+ # end
13
+ #
14
+ # # Output to the console
15
+ # # Success: expected false not to be true.
16
+ #
17
+ # @example The basic behavior of arrays
18
+ # require "r_spec"
19
+ #
20
+ # RSpec.describe Array do
21
+ # describe "#size" do
22
+ # it "correctly reports the number of elements in the Array" do
23
+ # expect([1, 2, 3].size).to eq 3
24
+ # end
25
+ # end
26
+ #
27
+ # describe "#empty?" do
28
+ # it "is empty when no elements are in the array" do
29
+ # expect([].empty?).to be_true
30
+ # end
31
+ #
32
+ # it "is not empty if there are elements in the array" do
33
+ # expect([1].empty?).to be_false
34
+ # end
35
+ # end
36
+ # end
37
+ #
38
+ # # Output to the console
39
+ # # Success: expected to eq 3.
40
+ # # Success: expected true to be true.
41
+ # # Success: expected false to be false.
6
42
  #
43
+ # @example An inherited definition of let
44
+ # require "r_spec"
45
+ #
46
+ # RSpec.describe Integer do
47
+ # let(:answer) { 42 }
48
+ #
49
+ # it "returns the value" do
50
+ # expect(answer).to be(42)
51
+ # end
52
+ #
53
+ # context "when the number is incremented" do
54
+ # let(:answer) { super().next }
55
+ #
56
+ # it "returns the next value" do
57
+ # expect(answer).to be(43)
58
+ # end
59
+ # end
60
+ # end
61
+ #
62
+ # # Output to the console
63
+ # # Success: expected to be 42.
64
+ # # Success: expected to be 43.
65
+ #
66
+ # @api public
7
67
  module RSpec
8
68
  # Specs are built with this method.
9
- def self.describe(const, &block)
10
- raise ::TypeError, const.class.inspect unless const.is_a?(::Module)
11
-
12
- DSL.describe(const, &block)
69
+ #
70
+ # @param const [Module, String] A module to include in block context.
71
+ # @param block [Proc] The block to define the specs.
72
+ #
73
+ # @api public
74
+ def self.describe(const = nil, &block)
75
+ Dsl.describe(const, &block)
13
76
  end
14
77
  end
15
-
16
- require_relative File.join("r_spec", "dsl")
@@ -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,28 +1,149 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "expresenter"
4
- require "matchi/rspec"
5
3
  require "securerandom"
6
4
 
5
+ require_relative "console"
6
+ require_relative "error"
7
+ require_relative "expectation_helper"
8
+ require_relative "sandbox"
9
+
7
10
  module RSpec
8
- class DSL
11
+ # Abstract class for handling the domain-specific language.
12
+ class Dsl
13
+ # Executes the given block before each spec in the current context runs.
14
+ #
15
+ # @example
16
+ # require "r_spec"
17
+ #
18
+ # RSpec.describe Integer do
19
+ # before do
20
+ # @value = 123
21
+ # end
22
+ #
23
+ # it { expect(@value).to be 123 }
24
+ #
25
+ # describe "nested" do
26
+ # before do
27
+ # @value -= 81
28
+ # end
29
+ #
30
+ # it { expect(@value).to be 42 }
31
+ # end
32
+ #
33
+ # it { expect(@value).to be 123 }
34
+ # end
35
+ #
36
+ # # Output to the console
37
+ # # Success: expected to be 123.
38
+ # # Success: expected to be 42.
39
+ # # Success: expected to be 123.
40
+ #
41
+ # @param block [Proc] The content to execute at the class initialization.
9
42
  def self.before(&block)
10
- define_method(:initialize) do |*args, **kwargs|
43
+ define_method(:initialize) do
11
44
  super()
12
- instance_exec(*args, **kwargs, &block)
45
+ instance_eval(&block)
13
46
  end
47
+
48
+ private :initialize
49
+ end
50
+
51
+ # Executes the given block after each spec in the current context runs.
52
+ #
53
+ # @example
54
+ # require "r_spec"
55
+ #
56
+ # RSpec.describe Integer do
57
+ # after do
58
+ # puts "That is the answer to everything."
59
+ # end
60
+ #
61
+ # it { expect(42).to be 42 }
62
+ # end
63
+ #
64
+ # # Output to the console
65
+ # # Success: expected to be 42.
66
+ # # That is the answer to everything.
67
+ #
68
+ # @param block [Proc] The content to execute at the class initialization.
69
+ def self.after(&block)
70
+ define_method(:terminate) do
71
+ instance_exec(&block)
72
+ super()
73
+ end
74
+
75
+ private :terminate
14
76
  end
15
77
 
16
- def self.let(name, &block)
17
- protected define_method(name.to_sym, &block)
78
+ # Sets a user-defined property.
79
+ #
80
+ # @example
81
+ # require "r_spec"
82
+ #
83
+ # RSpec.describe do
84
+ # let(:name) { "Bob" }
85
+ #
86
+ # it { expect(name).to eq "Bob" }
87
+ #
88
+ # context "with last name" do
89
+ # let(:name) { "#{super()} Smith" }
90
+ #
91
+ # it { expect(name).to eq "Bob Smith" }
92
+ # end
93
+ # end
94
+ #
95
+ # # Output to the console
96
+ # # Success: expected to eq "Bob".
97
+ # # Success: expected to eq "Bob Smith".
98
+ #
99
+ # @param name [String, Symbol] The name of the property.
100
+ # @param block [Proc] The content of the method to define.
101
+ #
102
+ # @return [Symbol] A protected method that define the block content.
103
+ def self.let(name, *args, **kwargs, &block)
104
+ protected define_method(name.to_sym, *args, **kwargs, &block)
18
105
  end
19
106
 
107
+ # Sets a user-defined property named {#subject}.
108
+ #
109
+ # @example
110
+ # require "r_spec"
111
+ #
112
+ # RSpec.describe Array do
113
+ # subject { [1, 2, 3] }
114
+ #
115
+ # it "has the prescribed elements" do
116
+ # expect(subject).to eq([1, 2, 3])
117
+ # end
118
+ # end
119
+ #
120
+ # # Output to the console
121
+ # # Success: expected to eq [1, 2, 3].
122
+ #
123
+ # @param block [Proc] The subject to set.
124
+ # @return [Symbol] A {#subject} method that define the block content.
20
125
  def self.subject(&block)
21
126
  let(__method__, &block)
22
127
  end
23
128
 
24
- def self.describe(const, &block)
25
- desc = Test.const_set("Test#{random_str}", ::Class.new(self))
129
+ # Defines an example group that describes a unit to be tested.
130
+ #
131
+ # @example
132
+ # require "r_spec"
133
+ #
134
+ # RSpec.describe String do
135
+ # describe "+" do
136
+ # it("concats") { expect("foo" + "bar").to eq "foobar" }
137
+ # end
138
+ # end
139
+ #
140
+ # # Output to the console
141
+ # # Success: expected to eq "foobar".
142
+ #
143
+ # @param const [Module, #object_id] A module to include in block context.
144
+ # @param block [Proc] The block to define the specs.
145
+ def self.describe(const = nil, &block)
146
+ desc = Sandbox.const_set(random_context_const_name, ::Class.new(self))
26
147
 
27
148
  if const.is_a?(::Module)
28
149
  desc.define_method(:described_class) { const }
@@ -30,51 +151,222 @@ module RSpec
30
151
  end
31
152
 
32
153
  desc.instance_eval(&block)
33
- desc
34
154
  end
35
155
 
36
- singleton_class.send(:alias_method, :context, :describe)
156
+ # Defines an example group that establishes a specific context, like _empty
157
+ # array_ versus _array with elements_.
158
+ #
159
+ # It is functionally equivalent to {.describe}.
160
+ #
161
+ # @example
162
+ # require "r_spec"
163
+ #
164
+ # RSpec.describe do
165
+ # context "when resource is not found" do
166
+ # pending "responds with 404"
167
+ # end
168
+ #
169
+ # context "when resource is found" do
170
+ # pending "responds with 200"
171
+ # end
172
+ # end
173
+ #
174
+ # # Output to the console
175
+ # # Warning: responds with 404.
176
+ # # Warning: responds with 200.
177
+ #
178
+ # @param _description [String] A description that usually begins with
179
+ # "when", "with" or "without".
180
+ # @param block [Proc] The block to define the specs.
181
+ def self.context(_description = nil, &block)
182
+ desc = Sandbox.const_set(random_context_const_name, ::Class.new(self))
183
+ desc.instance_eval(&block)
184
+ end
185
+
186
+ # Defines a concrete test case.
187
+ #
188
+ # The test is performed by the block supplied to `&block`.
189
+ #
190
+ # @example The integer after 41
191
+ # require "r_spec"
192
+ #
193
+ # RSpec.describe Integer do
194
+ # it { expect(41.next).to be 42 }
195
+ # end
196
+ #
197
+ # # Output to the console
198
+ # # Success: expected to be 42.
199
+ #
200
+ # @example A division by zero
201
+ # require "r_spec"
202
+ #
203
+ # RSpec.describe Integer do
204
+ # subject { 41 }
205
+ #
206
+ # it { is_expected.to be_an_instance_of described_class }
207
+ #
208
+ # it "raises an error" do
209
+ # expect { subject / 0 }.to raise_exception ZeroDivisionError
210
+ # end
211
+ # end
212
+ #
213
+ # # Output to the console
214
+ # # Success: expected 41 to be an instance of Integer.
215
+ # # Success: divided by 0.
216
+ #
217
+ # It can be used inside a {.describe} or {.context} section.
218
+ #
219
+ # @param _name [String, nil] The name of the spec.
220
+ # @param block [Proc] An expectation to evaluate.
221
+ #
222
+ # @raise (see ExpectationTarget::Base#result)
223
+ # @return (see ExpectationTarget::Base#result)
224
+ def self.it(_name = nil, &block)
225
+ raise ::ArgumentError, "Missing example block" unless block
37
226
 
38
- def self.it(name = "test_#{random_str.downcase}", &block)
39
- raise ::ArgumentError, "Missing block for #{name.inspect} test" unless block
227
+ i = it_example.new
228
+ i.instance_eval(&block)
229
+ rescue ::SystemExit
230
+ Console.source(*block.source_location)
231
+
232
+ exit false
233
+ ensure
234
+ i.send(:terminate)
235
+ end
40
236
 
41
- path_info = block.source_location.join(":")
42
- print "\e[3m#{path_info}\e[23m "
237
+ # Use the {.its} method to define a single spec that specifies the actual
238
+ # value of an attribute of the subject using
239
+ # {ExpectationHelper::Its#is_expected}.
240
+ #
241
+ # @example The integer after 41
242
+ # require "r_spec"
243
+ #
244
+ # RSpec.describe Integer do
245
+ # subject { 41 }
246
+ #
247
+ # its(:next) { is_expected.to be 42 }
248
+ # end
249
+ #
250
+ # # Output to the console
251
+ # # Success: expected to be 42.
252
+ #
253
+ # @example A division by zero
254
+ # require "r_spec"
255
+ #
256
+ # RSpec.describe Integer do
257
+ # subject { 41 }
258
+ #
259
+ # its(:/, 0) { is_expected.to raise_exception ZeroDivisionError }
260
+ # end
261
+ #
262
+ # # Output to the console
263
+ # # Success: divided by 0.
264
+ #
265
+ # @example A spec without subject
266
+ # require "r_spec"
267
+ #
268
+ # RSpec.describe Integer do
269
+ # its(:boom) { is_expected.to raise_exception RSpec::Error::UndefinedSubject }
270
+ # end
271
+ #
272
+ # # Output to the console
273
+ # # Success: subject not explicitly defined.
274
+ #
275
+ # @param attribute [String, Symbol] The property to call to subject.
276
+ # @param args [Array] An optional list of arguments.
277
+ # @param kwargs [Hash] An optional list of keyword arguments.
278
+ # @param block [Proc] An expectation to evaluate.
279
+ #
280
+ # @raise (see ExpectationTarget::Base#result)
281
+ # @return (see ExpectationTarget::Base#result)
282
+ def self.its(attribute, *args, **kwargs, &block)
283
+ raise ::ArgumentError, "Missing example block" unless block
284
+
285
+ i = its_example.new
286
+
287
+ i.define_singleton_method(:actual) do
288
+ subject.public_send(attribute, *args, **kwargs)
289
+ end
43
290
 
44
- i = example.new
45
291
  i.instance_eval(&block)
292
+ rescue ::SystemExit
293
+ Console.source(*block.source_location)
294
+
295
+ exit false
296
+ ensure
297
+ i.send(:terminate)
298
+ end
299
+
300
+ # Defines a pending test case.
301
+ #
302
+ # `&block` is never evaluated. It can be used to describe behaviour that is
303
+ # not yet implemented.
304
+ #
305
+ # @example
306
+ # require "r_spec"
307
+ #
308
+ # RSpec.describe do
309
+ # pending "is implemented but waiting" do
310
+ # expect("something").to eq("getting finished")
311
+ # end
312
+ # end
313
+ #
314
+ # # Output to the console
315
+ # # Warning: is implemented but waiting.
316
+ #
317
+ # @example
318
+ # require "r_spec"
319
+ #
320
+ # RSpec.describe do
321
+ # pending "is not yet implemented and waiting"
322
+ # end
323
+ #
324
+ # # Output to the console
325
+ # # Warning: is not yet implemented and waiting.
326
+ #
327
+ # @param message [String] The reason why the example is pending.
328
+ #
329
+ # @return [nil] Write a message to STDOUT.
330
+ #
331
+ # @api public
332
+ def self.pending(message)
333
+ Console.passed_spec Error::PendingExpectation.result(message)
46
334
  end
47
335
 
48
- private_class_method def self.example
49
- ::Class.new(self) do
50
- include ::Matchi::Helper
336
+ # @private
337
+ #
338
+ # @return [Class<Dsl>] The class of the example to be tested.
339
+ def self.it_example
340
+ ::Class.new(self) { include ExpectationHelper::It }
341
+ end
51
342
 
52
- private
343
+ # @private
344
+ #
345
+ # @return [Class<Dsl>] The class of the example to be tested.
346
+ def self.its_example
347
+ ::Class.new(self) { include ExpectationHelper::Its }
348
+ end
53
349
 
54
- def expect(actual)
55
- undef expect
56
- undef is_expected
350
+ # @private
351
+ #
352
+ # @return [String] A random constant name for a test class.
353
+ def self.random_context_const_name
354
+ "Context#{::SecureRandom.hex(4).to_i(16)}"
355
+ end
57
356
 
58
- Expect.new(actual)
59
- end
357
+ private_class_method :it_example, :its_example, :random_context_const_name
60
358
 
61
- # rubocop:disable Naming/PredicateName
62
- def is_expected
63
- expect(subject)
64
- end
65
- # rubocop:enable Naming/PredicateName
359
+ protected
66
360
 
67
- def pending(description)
68
- puts Requirement.pending(description).colored_string
69
- end
70
- end
361
+ def described_class
362
+ raise Error::UndefinedDescribedClass,
363
+ "the first argument to at least one example group must be a module"
71
364
  end
72
365
 
73
- private_class_method def self.random_str
74
- ::SecureRandom.alphanumeric(5)
366
+ def subject
367
+ raise Error::UndefinedSubject, "subject not explicitly defined"
75
368
  end
369
+
370
+ def terminate; end
76
371
  end
77
372
  end
78
-
79
- require_relative "expect"
80
- require_relative "test"