r_spec 1.0.0.beta1 → 1.0.0.beta6

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: 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"