r_spec 1.0.0.beta2 → 1.0.0.beta7

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