r_spec 1.0.0.beta2 → 1.0.0.beta7

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: d95780dc86d2b0c2295f22fe18ed889298716d8a817ba4a74fbd1c9c1ec71c03
4
- data.tar.gz: a954787c7ad5a50c1d3a49d5b1be21515dc0cad0ecd5f7fa618ee736014add2f
3
+ metadata.gz: 46557ee64f6cc554942399aa9219e9102879b2b7b113e53dc4ba63d2c0c5c0af
4
+ data.tar.gz: a83b773e20cf2db46b28586be6836d7913a4c3b8f9bf2ce747110f2556737374
5
5
  SHA512:
6
- metadata.gz: 338b22198f2f09e9ae3a36184027f23f728ebdc7b691e0ef3f05d111bf3d753c829d21388dba52fdc8be0e8ba452deea5cc08d38c848e932d67fb1e448769600
7
- data.tar.gz: 6601841bac1ed4603c327539fc5b123b9990cfa483b42504ed18dc8680d9c304636dbff10a05ebdc6f76de6d11ac3190b9ad77453aaeb978f853360f9330e04e
6
+ metadata.gz: b7c89ce9b933e7adcfc26e03c2f4417ed44c423ca56932f173b2161572877967df37d99c9a560e8cf64c58458777842cb0db7eb1396c89a7f052d981b45f9864
7
+ data.tar.gz: 51c4b747116d8f1bb3bb2e593b00aa940ec463d1b9a03506984f2b445f3607febd455884051279f73cef2fc2edac7cfe6d9b4de210c1f1cef6402bd501729f6c
data/README.md CHANGED
@@ -1,27 +1,38 @@
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
- ## Goal
14
+ ## Project Goals
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
+ * Enforce the guidelines and best practices outlined in the community [RSpec style guide](https://rspec.rubystyle.guide/).
17
+ * Provide most of RSpec's DSL to express expected outcomes of a code example without magic power.
16
18
 
17
- ## Some differences
19
+ ## Some Differences
18
20
 
19
21
  * Less features and an implementation with much less code complexity.
20
22
  * Spec files can also be executed directly with the `ruby` executable.
21
23
  * There is no option to activate monkey-patching.
22
- * Does not rely on hacks such as `at_exit` hook to trigger the tests.
23
- * Built-in matchers do not trust _actual_ and do not send it any message.
24
- * The subject must be explicitly defined, otherwise it is not implemented.
24
+ * It does not rely on hacks such as `at_exit` hook to trigger the tests.
25
+ * Built-in matchers do not trust _actual_ and do not send it messages.
26
+ * If no `subject` has been explicitly determined, none is defined.
27
+ * If no described class is set, `described_class` is undefined instead of `nil`.
28
+ * Expectations cannot be added inside a `before` block.
29
+ * [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not exposed to examples.
30
+ * The `let` method defines a helper method rather than a memoized helper method.
31
+ * The one-liner `is_expected` syntax also works with block expectations.
32
+ * All `subject` definitions must come _before_ examples.
33
+ * All `before` definitions must come _before_ examples.
34
+ * All `after` definitions must come _before_ examples.
35
+ * All `let` definitions must come _before_ examples.
25
36
 
26
37
  ## Important ⚠️
27
38
 
@@ -40,7 +51,7 @@ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-ge
40
51
  Add this line to your application's Gemfile:
41
52
 
42
53
  ```ruby
43
- gem "r_spec", ">= 1.0.0.beta2"
54
+ gem "r_spec", ">= 1.0.0.beta7"
44
55
  ```
45
56
 
46
57
  And then execute:
@@ -55,48 +66,172 @@ Or install it yourself as:
55
66
  gem install r_spec --pre
56
67
  ```
57
68
 
69
+ ## Overview
70
+
71
+ __RSpec clone__ provides a structure for writing executable examples of how your code should behave.
72
+
73
+ 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.
74
+
75
+ A basic spec looks something like this:
76
+
77
+ [![Super DRY example](https://asciinema.org/a/418672.svg)](https://asciinema.org/a/418672?autoplay=1)
78
+
58
79
  ## Usage
59
80
 
60
- Let's test an array:
81
+ ### Anatomy of a spec file
82
+
83
+ To use the `RSpec` module and its DSL, you need to add `require "r_spec"` to your spec files.
84
+ Many projects use a custom spec helper which organizes these includes.
85
+
86
+ Concrete test cases are defined in `it` blocks.
87
+ An optional descriptive string states it's purpose and a block contains the main logic performing the test.
88
+
89
+ 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.
90
+
91
+ An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
92
+ Each example can contain multiple expectations, but it should test only one specific behaviour.
93
+
94
+ To express an expectation, wrap an object or block in `expect`, call `to` (or `not_to`) and pass it a matcher object.
95
+ If the expectation is met, code execution continues.
96
+ Otherwise the example has _failed_ and other code will not be executed.
97
+
98
+ In test files, specs are structured by example groups which are defined by `describe` and `context` sections.
99
+ Typically a top level `describe` defines the outer unit (such as a class) to be tested by the spec.
100
+ Further `describe` sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
101
+
102
+ For unit tests, it is recommended to follow the conventions for method names:
103
+
104
+ * outer `describe` is the name of the class, inner `describe` targets methods;
105
+ * instance methods are prefixed with `#`, class methods with `.`.
106
+
107
+ To establish certain contexts — think _empty array_ versus _array with elements_ — the `context` method may be used to communicate this to the reader.
108
+ It has a different name, but behaves exactly like `describe`.
109
+
110
+ `describe` and `context` take an optional description as argument and a block containing the individual specs or nested groupings.
111
+
112
+ ### Expectations
113
+
114
+ Expectations define if the value being tested (_actual_) matches a certain value or specific criteria.
115
+
116
+ #### Equivalence
61
117
 
62
118
  ```ruby
63
- # array_spec.rb
119
+ expect(actual).to eql(expected) # passes if expected.eql?(actual)
120
+ expect(actual).to eq(expected) # passes if expected.eql?(actual)
121
+ ```
64
122
 
65
- require "r_spec"
123
+ #### Identity
66
124
 
67
- RSpec.describe Array do
68
- before do
69
- @elements = described_class.new
70
- end
125
+ ```ruby
126
+ expect(actual).to equal(expected) # passes if expected.equal?(actual)
127
+ expect(actual).to be(expected) # passes if expected.equal?(actual)
128
+ ```
71
129
 
72
- describe "#count" do
73
- subject do
74
- @elements.count
75
- end
130
+ #### Regular expressions
76
131
 
77
- it { is_expected.to be 0 }
132
+ ```ruby
133
+ expect(actual).to match(expected) # passes if expected.match?(actual)
134
+ ```
78
135
 
79
- context "when a new element is added" do
80
- before do
81
- @elements << 1
82
- end
136
+ #### Expecting errors
83
137
 
84
- it { is_expected.to be 1 }
85
- end
86
- end
87
- end
138
+ ```ruby
139
+ expect { actual }.to raise_exception(expected) # passes if expected exception is raised
140
+ ```
141
+
142
+ #### Truth
143
+
144
+ ```ruby
145
+ expect(actual).to be_true # passes if true.equal?(actual)
146
+ ```
147
+
148
+ #### Untruth
149
+
150
+ ```ruby
151
+ expect(actual).to be_false # passes if false.equal?(actual)
152
+ ```
153
+
154
+ #### Nil
155
+
156
+ ```ruby
157
+ expect(actual).to be_nil # passes if nil.equal?(actual)
88
158
  ```
89
159
 
90
- It can be tested in the console with the command:
160
+ #### Type/class
161
+
162
+ ```ruby
163
+ expect(actual).to be_instance_of(expected) # passes if expected.equal?(actual.class)
164
+ expect(actual).to be_an_instance_of(expected) # passes if expected.equal?(actual.class)
165
+ ```
166
+
167
+ ### Running specs
168
+
169
+ By convention, specs live in the `spec/` directory of a project. Spec files should end with `_spec.rb` to be recognizable as such.
170
+
171
+ 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).
172
+ A single file can also be executed directly with the Ruby interpreter.
173
+
174
+ #### Examples
175
+
176
+ Run all specs in files matching `spec/**/*_spec.rb`:
177
+
178
+ ```sh
179
+ bundle exec rake spec
180
+ ```
181
+
182
+ Run a single file:
91
183
 
92
184
  ```sh
93
- ruby array_spec.rb
185
+ ruby spec/my/test/file_spec.rb
186
+ ```
187
+
188
+ I know that sounds weird, but the [`rspec` command line](https://relishapp.com/rspec/rspec-core/docs/command-line) is also working pretty well:
189
+
190
+ ```sh
191
+ rspec spec/my/test/file_spec.rb
192
+ rspec spec/my/test/file_spec.rb:42
193
+ rspec spec/my/test/
194
+ rspec
195
+ ```
196
+
197
+ ### Spec helper
198
+
199
+ Many projects use a custom spec helper file, usually named `spec/spec_helper.rb`.
200
+
201
+ This file is used to require `r_spec` and other includes, like the code from the project needed for every spec file.
202
+
203
+ ### `rake` integration example
204
+
205
+ The following `Rakefile` settings should be enough:
206
+
207
+ ```ruby
208
+ require "bundler/gem_tasks"
209
+ require "rake/testtask"
210
+
211
+ Rake::TestTask.new do |t|
212
+ t.pattern = "spec/**/*_spec.rb"
213
+ t.verbose = true
214
+ t.warning = true
215
+ end
216
+
217
+ task spec: :test
218
+ task default: :test
94
219
  ```
95
220
 
96
- array_spec.rb:15 Success: expected to be 0.
97
- array_spec.rb:22 Success: expected to be 1.
221
+ ## Performance
98
222
 
99
- ## Test suite
223
+ ### Runtime
224
+
225
+ Benchmark against [100 executions of a file containing one expectation](https://github.com/cyril/r_spec.rb/blob/main/benchmark/) (lower is better).
226
+
227
+ | Framework | Seconds to complete |
228
+ |-------------|---------------------|
229
+ | `r_spec` | 13.0 |
230
+ | `rspec` | 32.2 |
231
+ | `minitest` | 17.5 |
232
+ | `test-unit` | 20.5 |
233
+
234
+ ## Test Suite
100
235
 
101
236
  __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
102
237
 
@@ -104,7 +239,20 @@ __RSpec clone__'s specifications are self-described here: [spec/](https://github
104
239
 
105
240
  * Home page: https://r-spec.dev
106
241
  * Source code: https://github.com/cyril/r_spec.rb
107
- * Twitter: https://twitter.com/cyri_
242
+ * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
243
+
244
+ ## Special Thanks ❤️
245
+
246
+ I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
247
+ It's a great framework and it's a pleasure to work with every day.
248
+
249
+ Without RSpec, this clone would not have been possible.
250
+
251
+ ## Buy me a Coffee ☕
252
+
253
+ If you like this project please consider making a small donation.
254
+
255
+ [![Donate with Ethereum](https://github.com/cyril/r_spec.rb/raw/main/img/donate-eth.svg)](https://etherscan.io/address/0x834b5c1feaff5aebf9cd0f25dc38e741d65ab773)
108
256
 
109
257
  ## Versioning
110
258
 
data/lib/r_spec.rb CHANGED
@@ -1,27 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative File.join("r_spec", "dsl")
4
+
3
5
  # Top level namespace for the RSpec clone.
4
6
  #
5
- # @example
7
+ # @example The true from the false
8
+ # require "r_spec"
9
+ #
10
+ # RSpec.describe "The true from the false" 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.
42
+ #
43
+ # @example An inherited definition of let
6
44
  # require "r_spec"
7
45
  #
8
46
  # RSpec.describe Integer do
9
- # it { expect(41.next).to be 42 }
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
10
60
  # end
11
61
  #
62
+ # # Output to the console
63
+ # # Success: expected to be 42.
64
+ # # Success: expected to be 43.
65
+ #
12
66
  # @api public
13
67
  module RSpec
14
68
  # Specs are built with this method.
15
69
  #
16
- # @param const [Module] A module to include in block context.
70
+ # @param const [Module, String] A module to include in block context.
17
71
  # @param block [Proc] The block to define the specs.
18
72
  #
19
73
  # @api public
20
74
  def self.describe(const, &block)
21
- raise ::TypeError, const.class.inspect unless const.is_a?(::Module)
22
-
23
- DSL.describe(const, &block)
75
+ Dsl.describe(const, &block)
24
76
  end
25
77
  end
26
-
27
- 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,114 +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
12
46
  super()
13
- instance_exec(*args, **kwargs, &block)
47
+ instance_eval(&block)
14
48
  end
49
+
50
+ private BEFORE_METHOD
15
51
  end
16
52
 
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)
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)
74
+ super()
75
+ end
76
+
77
+ private AFTER_METHOD
21
78
  end
22
79
 
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)
109
+ end
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("Test#{random_str}", ::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
- 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
46
184
 
47
- # 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
48
211
  #
212
+ # # Output to the console
213
+ # # Success: expected 41 to be an instance of Integer.
214
+ # # Success: divided by 0.
215
+ #
216
+ # It can be used inside a {.describe} or {.context} section.
217
+ #
218
+ # @param _name [String, nil] The name of the spec.
49
219
  # @param block [Proc] An expectation to evaluate.
50
220
  #
51
- # @raise (see ExpectationTarget#result)
52
- # @return (see ExpectationTarget#result)
221
+ # @raise (see ExpectationTarget::Base#result)
222
+ # @return (see ExpectationTarget::Base#result)
53
223
  def self.it(_name = nil, &block)
54
- raise ::ArgumentError, "Missing block" unless block
224
+ raise ::ArgumentError, "Missing example block" unless block
55
225
 
56
- 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)
57
230
 
58
- i = example.new
59
- i.instance_eval(&block)
231
+ exit false
232
+ ensure
233
+ example.send(AFTER_METHOD)
60
234
  end
61
235
 
62
- # @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}.
63
239
  #
64
- # @return [Class<DSL>] The class of the example to be tested.
65
- private_class_method def self.example
66
- ::Class.new(self) do
67
- include ::Matchi::Helper
68
-
69
- 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
70
283
 
71
- # Wraps the target of an expectation with the actual value.
72
- #
73
- # @param actual [#object_id] The actual value.
74
- #
75
- # @return [ExpectationTarget] The target of the expectation.
76
- def expect(actual)
77
- ExpectationTarget.new(actual)
78
- end
284
+ example = ::Class.new(self) do
285
+ include ExpectationHelper::Its
79
286
 
80
- # Wraps the target of an expectation with the subject as actual value.
81
- #
82
- # @return [ExpectationTarget] (see #expect)
83
- def is_expected
84
- expect(subject)
287
+ define_method(:actual) do
288
+ subject.public_send(attribute, *args, **kwargs)
85
289
  end
290
+ end.new
86
291
 
87
- def log(message)
88
- Log.result(message)
89
- end
292
+ example.instance_eval(&block)
293
+ rescue ::SystemExit
294
+ Console.source(*block.source_location)
90
295
 
91
- # Indicate that an example is disabled pending some action.
92
- #
93
- # @param description [String] The reason why the example is pending.
94
- #
95
- # @return [nil] Write a message to STDOUT.
96
- def pending(description)
97
- Pending.result(description)
98
- end
99
- end
296
+ exit false
297
+ ensure
298
+ example.send(AFTER_METHOD)
100
299
  end
101
300
 
102
- # @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
313
+ #
314
+ # pending "is not yet implemented and waiting"
315
+ # end
103
316
  #
104
- # @return [String] A random string.
105
- private_class_method def self.random_str
106
- ::SecureRandom.alphanumeric(5)
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)
328
+ end
329
+
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
107
343
  end
108
344
  end
109
345
  end
110
-
111
- require_relative "expectation_target"
112
- require_relative "log"
113
- require_relative "pending"
114
- require_relative "test"