r_spec 0.3.3 → 1.0.0.beta5

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: c83f214821947d01cf3be95393f5c7022c761ab5a97835128737be945a9ad75e
4
- data.tar.gz: fba08905b9fe487f5bd1f673857eb0b205bb070047252f0a007a815ef1f1aa69
3
+ metadata.gz: 5df8b9cdb685bc7388fdbdbc0a7de755a3c5c2426e5c6f8346c464562ed16e30
4
+ data.tar.gz: 9b22d8599741dd00b0a812931cbf4c575acf948b0e4914dd662decc23fd8eea6
5
5
  SHA512:
6
- metadata.gz: '0169eb3aab724b95880a3ba305b1b16506fc3950e0a635014c17de3f6162d80b8052ce7def9c4f5b955355bf288948f1951a52fdd37959f8e4a92cc26c0c9c61'
7
- data.tar.gz: 20d3e9b7eac17839452e9f9891d38b606ccdb91c87c5cba14beab05cbfdb536e30761418662c4449d8852fd823da99d43b541a2878f957a4d72c0bfdb7b1dc39
6
+ metadata.gz: 27938ad6a4ab512ef8c564402064188c67bfb40430897f14be5cbd07eefae04bce7bf5709b5a4fbb11f82b4e51b9aaaaa36278ad3ff34cc829fd54a76d643660
7
+ data.tar.gz: b2ee9bccf5f59bb9d1a0a03d800a8824f643b5875854e57465c640d5eacce8a1ae86a45ccfe42c968921c6587a07d973b1e622bf19ac9e19eee22d906d396f16
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2019 Cyril Kato
3
+ Copyright (c) 2015-2021 Cyril Kato
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,129 +1,246 @@
1
- # R Spec
1
+ # RSpec clone
2
2
 
3
- [![Build Status](https://travis-ci.org/cyril/r_spec.svg?branch=master)][travis]
4
- [![Code Climate](https://codeclimate.com/github/cyril/r_spec/badges/gpa.svg)][codeclimate]
5
- [![Gem Version](https://badge.fury.io/rb/r_spec.svg)][gem]
6
- [![Inline docs](https://inch-ci.org/github/cyril/r_spec.svg?branch=master)][inchpages]
7
- [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
3
+ A minimalist __[RSpec](https://github.com/rspec/rspec) clone__ with all the essentials.
8
4
 
9
- > A small [Rspec](https://github.com/rspec/rspec) clone based on [Fix specing framework](https://github.com/fixrb/fix).
5
+ ![What did you RSpec?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-rspec.svg)
10
6
 
11
- ***
7
+ ## Status
12
8
 
13
- :warning: Important:
9
+ [![Gem Version](https://badge.fury.io/rb/r_spec.svg)](https://badge.fury.io/rb/r_spec)
10
+ [![Build Status](https://travis-ci.org/cyril/r_spec.rb.svg?branch=main)](https://travis-ci.org/cyril/r_spec.rb)
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)
14
13
 
15
- To avoid confusion in the community, please note that the gem of this project is **not [rspec](https://rubygems.org/gems/rspec)**, it is **[r_spec](https://rubygems.org/gems/r_spec)** (meaning _Ruby Spec_).
14
+ ## Goal
16
15
 
17
- This project is totally independent of [rspec.info](http://rspec.info/).
16
+ This clone attempts to provide most of RSpec's DSL to express expected outcomes of a code example without magic power.
18
17
 
19
- Also, while both gems define an `RSpec` module, **r_spec** (which follows [the gem naming convention](https://guides.rubygems.org/name-your-gem/#use-underscores-for-multiple-words)) is still quite different than **rspec** due to its [Ruby Fix](https://fixrb.dev/) dependency.
18
+ ## Some differences
20
19
 
21
- ***
20
+ * Less features and an implementation with much less code complexity.
21
+ * Spec files can also be executed directly with the `ruby` executable.
22
+ * There is no option to activate monkey-patching.
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.
22
31
 
23
- ## Contact
32
+ ## Important ⚠️
24
33
 
25
- * Home page: https://github.com/cyril/r_spec
26
- * Bugs/issues: https://github.com/cyril/r_spec/issues
34
+ To avoid confusion in the community, please note that:
27
35
 
28
- ## Rubies
36
+ - the gem of this project is **not [`rspec`](https://rubygems.org/gems/rspec)**,
37
+ it is **[`r_spec`](https://rubygems.org/gems/r_spec)**;
38
+ - this project is totally independent of [rspec.info](https://rspec.info/).
29
39
 
30
- * [MRI](https://www.ruby-lang.org/)
31
- * [Rubinius](https://rubinius.com/)
32
- * [JRuby](https://www.jruby.org/)
40
+ ### Note
33
41
 
34
- ## Terminal sessions
42
+ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-gem/#use-underscores-for-multiple-words), the module name for this project is `RSpec`.
35
43
 
36
- A comparison between the behavior of a small fix-based script (which became the `r_spec` gem) and `rspec`.
44
+ ## Installation
37
45
 
38
- As a result, with `r_spec` the build is passing, while with rspec it is failing. Despite two separate contexts, `rspec` was not able to evaluate some code in isolation to prevent side effects.
46
+ Add this line to your application's Gemfile:
39
47
 
40
- [![What did you RSpec?](https://asciinema.org/a/29070.png)](https://asciinema.org/a/29070)
48
+ ```ruby
49
+ gem "r_spec", ">= 1.0.0.beta5"
50
+ ```
41
51
 
42
- ***
52
+ And then execute:
43
53
 
44
- Unbelievable but true, sometimes "💩" is equal to 42.
54
+ ```sh
55
+ bundle
56
+ ```
45
57
 
46
- Although fun, this _feature_ can be fixed pretty easily...
58
+ Or install it yourself as:
47
59
 
48
- [![Fix RSpec!](https://asciinema.org/a/29172.png)](https://asciinema.org/a/29172)
60
+ ```sh
61
+ gem install r_spec --pre
62
+ ```
49
63
 
50
- ## Installation
64
+ ## Overview
51
65
 
52
- Add this line to your application's Gemfile:
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
+
74
+ ## Usage
75
+
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
+ An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
85
+ Each example can contain multiple expectations, but it should test only one specific behaviour.
86
+
87
+ To express an expectation, wrap an object or block in `expect`, call `to` or `not_to` and pass it a matcher object.
88
+
89
+ If the expectation is met, code execution continues.
90
+ Otherwise the example has _failed_ and other code will not be executed.
91
+
92
+ Test cases that have been defined or outlined but are not yet expected to work can be defined using `pending` instead of `expect`. They will not be run but show up in the spec report as pending.
93
+
94
+ In test files, specs are structured by example groups which are defined by `describe` and `context` sections.
95
+ Typically a top level `describe` defines the outer unit (such as a class) to be tested by the spec.
96
+ Further `describe` sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
97
+
98
+ For unit tests, it is recommended to follow the conventions for method names:
99
+
100
+ * outer `describe` is the name of the class, inner `describe` targets methods;
101
+ * instance methods are prefixed with `#`, class methods with `.`.
102
+
103
+ To establish certain contexts - think _empty array_ versus _array with elements_ - the `context` method may be used to communicate this to the reader.
104
+ It has a different name, but behaves exactly like `describe`.
105
+
106
+ `describe` and `context` take an optional description as argument and a block containing the individual specs or nested groupings.
107
+
108
+ ### Expectations
109
+
110
+ Expectations define if the value being tested (_actual_) matches a certain value or specific criteria.
111
+
112
+ #### Equivalence
53
113
 
54
114
  ```ruby
55
- gem 'r_spec'
115
+ expect(actual).to eql(expected) # passes if expected.eql?(actual)
116
+ expect(actual).to eq(expected) # passes if expected.eql?(actual)
56
117
  ```
57
118
 
58
- And then execute:
119
+ #### Identity
59
120
 
60
- $ bundle
121
+ ```ruby
122
+ expect(actual).to equal(expected) # passes if expected.equal?(actual)
123
+ expect(actual).to be(expected) # passes if expected.equal?(actual)
124
+ ```
61
125
 
62
- Or install it yourself as:
126
+ #### Regular expressions
63
127
 
64
- $ gem install r_spec
128
+ ```ruby
129
+ expect(actual).to match(expected) # passes if expected.match?(actual)
130
+ ```
65
131
 
66
- ## Usage
132
+ #### Expecting errors
133
+
134
+ ```ruby
135
+ expect { actual }.to raise_exception(expected) # passes if expected exception is raised
136
+ ```
137
+
138
+ #### Truth
139
+
140
+ ```ruby
141
+ expect(actual).to be_true # passes if true.equal?(actual)
142
+ ```
67
143
 
68
- Given this `greeting_spec.rb` spec:
144
+ #### Untruth
69
145
 
70
146
  ```ruby
71
- require 'r_spec'
147
+ expect(actual).to be_false # passes if false.equal?(actual)
148
+ ```
72
149
 
73
- greeting = 'Hello, world!'
150
+ #### Nil
74
151
 
75
- RSpec.describe 'Test' do
76
- context 'Alice' do
77
- before { greeting.gsub!('world', 'Alice') }
78
- it { expect(greeting).to eql 'Hello, Alice!' }
79
- end
152
+ ```ruby
153
+ expect(actual).to be_nil # passes if nil.equal?(actual)
154
+ ```
80
155
 
81
- context 'Bob' do
82
- before { greeting.gsub!('world', 'Bob') }
83
- it { expect(greeting).to eql 'Hello, Bob!' }
84
- end
85
- end
156
+ #### Type/class
157
+
158
+ ```ruby
159
+ expect(actual).to be_instance_of(expected) # passes if expected.equal?(actual.class)
160
+ expect(actual).to be_an_instance_of(expected) # passes if expected.equal?(actual.class)
86
161
  ```
87
162
 
88
- It can be tested in the console with the command:
163
+ ### Running specs
164
+
165
+ By convention, specs live in the `spec/` directory of a project. Spec files should end with `_spec.rb` to be recognizable as such.
166
+
167
+ 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).
168
+ A single file can also be executed directly with the Ruby interpreter.
169
+
170
+ #### Examples
89
171
 
90
- $ ruby greeting_spec.rb
91
- ..
172
+ Run all specs in files matching `spec/**/*_spec.rb`:
92
173
 
93
- Ran 2 tests in 0.010994 seconds
94
- 100% compliant - 0 infos, 0 failures, 0 errors
174
+ ```sh
175
+ bundle exec rake spec
176
+ ```
177
+
178
+ Run a single file:
95
179
 
96
- ## Security
180
+ ```sh
181
+ ruby spec/my/test/file_spec.rb
182
+ ```
97
183
 
98
- As a basic form of security __R Spec__ provides a set of SHA512 checksums for
99
- every Gem release. These checksums can be found in the `checksum/` directory.
100
- Although these checksums do not prevent malicious users from tampering with a
101
- built Gem they can be used for basic integrity verification purposes.
184
+ I know that sounds weird, but the [`rspec` command line](https://relishapp.com/rspec/rspec-core/docs/command-line) is also working pretty well:
185
+
186
+ ```sh
187
+ rspec spec/my/test/file_spec.rb
188
+ rspec spec/my/test/file_spec.rb:42
189
+ rspec spec/my/test/
190
+ rspec
191
+ ```
102
192
 
103
- The checksum of a file can be checked using the `sha512sum` command. For
104
- example:
193
+ ### Spec helper
105
194
 
106
- $ sha512sum pkg/r_spec-0.1.0.gem
107
- e9e35e1953104e2d428b0f217e418db3c1baecd9e011b2545f9fcba4ff7e3bba674c6b928b3d8db842a139cd7cc9806d77ebdc7f710ece4f2aecb343703e2451 pkg/r_spec-0.1.0.gem
195
+ Many projects use a custom spec helper file, usually named `spec/spec_helper.rb`.
196
+
197
+ This file is used to require `r_spec` and other includes, like the code from the project needed for every spec file.
198
+
199
+ ### `rake` integration example
200
+
201
+ The following `Rakefile` settings should be enough:
202
+
203
+ ```ruby
204
+ require "bundler/gem_tasks"
205
+ require "rake/testtask"
206
+
207
+ Rake::TestTask.new do |t|
208
+ t.pattern = "spec/**/*_spec.rb"
209
+ t.verbose = true
210
+ t.warning = true
211
+ end
212
+
213
+ task spec: :test
214
+ task default: :test
215
+ ```
216
+
217
+ ## Test suite
218
+
219
+ __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
220
+
221
+ ## Contact
222
+
223
+ * Home page: https://r-spec.dev
224
+ * Source code: https://github.com/cyril/r_spec.rb
225
+ * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
108
226
 
109
227
  ## Versioning
110
228
 
111
- __R Spec__ follows [Semantic Versioning 2.0](https://semver.org/).
229
+ __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
112
230
 
113
- ## Contributing
231
+ ## Special thanks ❤️
114
232
 
115
- 1. [Fork it](https://github.com/cyril/r_spec/fork)
116
- 2. Create your feature branch (`git checkout -b my-new-feature`)
117
- 3. Commit your changes (`git commit -am 'Add some feature'`)
118
- 4. Push to the branch (`git push origin my-new-feature`)
119
- 5. Create a new Pull Request
233
+ I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
234
+ It's a great framework and it's a pleasure to work with every day.
120
235
 
121
- ## License
236
+ Without RSpec, this clone would not have been possible.
122
237
 
123
- See `LICENSE.md` file.
238
+ ## Buy me a coffee ☕
239
+
240
+ If you like this project please consider making a small donation.
241
+
242
+ [![Donate with Ethereum](https://github.com/cyril/r_spec.rb/raw/main/img/donate-eth.svg)](https://etherscan.io/address/0x834b5c1feaff5aebf9cd0f25dc38e741d65ab773)
243
+
244
+ ## License
124
245
 
125
- [gem]: https://rubygems.org/gems/r_spec
126
- [travis]: https://travis-ci.org/cyril/r_spec
127
- [codeclimate]: https://codeclimate.com/github/cyril/r_spec
128
- [inchpages]: https://inch-ci.org/github/cyril/r_spec
129
- [rubydoc]: https://www.rubydoc.info/gems/r_spec/frames
246
+ The [gem](https://rubygems.org/gems/r_spec) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/lib/r_spec.rb CHANGED
@@ -1,33 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'matchi/rspec'
4
- require 'fix/expect'
5
- require 'fix/its'
6
-
7
- # Namespace for the R Spec framework.
3
+ # Top level namespace for the RSpec clone.
8
4
  #
9
- # @api public
5
+ # @example The true from the false
6
+ # require "r_spec"
7
+ #
8
+ # RSpec.describe do
9
+ # it { expect(false).not_to be true }
10
+ # end
11
+ #
12
+ # # Output to the console
13
+ # # Success: expected false not to be true.
14
+ #
15
+ # @example The basic behavior of arrays
16
+ # require "r_spec"
17
+ #
18
+ # RSpec.describe Array do
19
+ # describe "#size" do
20
+ # it "correctly reports the number of elements in the Array" do
21
+ # expect([1, 2, 3].size).to eq 3
22
+ # end
23
+ # end
24
+ #
25
+ # describe "#empty?" do
26
+ # it "is empty when no elements are in the array" do
27
+ # expect([].empty?).to be_true
28
+ # end
29
+ #
30
+ # it "is not empty if there are elements in the array" do
31
+ # expect([1].empty?).to be_false
32
+ # end
33
+ # end
34
+ # end
35
+ #
36
+ # # Output to the console
37
+ # # Success: expected to eq 3.
38
+ # # Success: expected true to be true.
39
+ # # Success: expected false to be false.
10
40
  #
41
+ # @example An inherited definition of let
42
+ # require "r_spec"
43
+ #
44
+ # RSpec.describe Integer do
45
+ # let(:answer) { 42 }
46
+ #
47
+ # it "returns the value" do
48
+ # expect(answer).to be(42)
49
+ # end
50
+ #
51
+ # context "when the number is incremented" do
52
+ # let(:answer) { super().next }
53
+ #
54
+ # it "returns the next value" do
55
+ # expect(answer).to be(43)
56
+ # end
57
+ # end
58
+ # end
59
+ #
60
+ # # Output to the console
61
+ # # Success: expected to be 42.
62
+ # # Success: expected to be 43.
63
+ #
64
+ # @api public
11
65
  module RSpec
12
66
  # Specs are built with this method.
13
67
  #
14
- # @example The answer must be equal to 42.
15
- # describe('the answer') do
16
- # it { expect(42).to be 42 }
17
- # end
68
+ # @param const [Module, String] A module to include in block context.
69
+ # @param block [Proc] The block to define the specs.
18
70
  #
19
- # @param front_object [#object_id] The front object.
20
- # @param options [Hash] Some options.
21
- # @param specs [Proc] The set of specs.
22
- #
23
- # @raise [SystemExit] The result of the test.
24
- def self.describe(front_object, options = {}, &specs)
25
- t = ::Fix::Test.new(front_object, options, &specs)
26
-
27
- print t.report.to_s if options.fetch(:verbose, true)
28
- exit t.pass?
71
+ # @api public
72
+ def self.describe(const = nil, &block)
73
+ Dsl.describe(const, &block)
29
74
  end
30
75
  end
31
76
 
32
- require_relative File.join 'fix', 'it'
33
- require_relative File.join 'fix', 'on'
77
+ require_relative File.join("r_spec", "dsl")
data/lib/r_spec/dsl.rb ADDED
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module RSpec
6
+ # Abstract class for handling the domain-specific language.
7
+ class Dsl
8
+ # Instructs the spec runner to execute the given block before each spec in
9
+ # the spec suite.
10
+ #
11
+ # @param block [Proc] The content to execute at the class initialization.
12
+ def self.before(&block)
13
+ define_method(:initialize) do |*args, **kwargs|
14
+ super()
15
+ instance_exec(*args, **kwargs, &block)
16
+ end
17
+ end
18
+
19
+ # Sets a user-defined property.
20
+ #
21
+ # @param name [String, Symbol] The name of the property.
22
+ # @param block [Proc] The content of the method to define.
23
+ #
24
+ # @return [Symbol] A protected method that define the block content.
25
+ def self.let(name, *args, **kwargs, &block)
26
+ protected define_method(name.to_sym, *args, **kwargs, &block)
27
+ end
28
+
29
+ # Sets a user-defined property named `subject`.
30
+ #
31
+ # @param block [Proc] The subject to set.
32
+ # @return [Symbol] A `subject` method that define the block content.
33
+ def self.subject(&block)
34
+ let(__method__, &block)
35
+ end
36
+
37
+ # Create a group of specs.
38
+ #
39
+ # @param const [Module, #object_id] A module to include in block context.
40
+ # @param block [Proc] The block to define the specs.
41
+ def self.describe(const = nil, &block)
42
+ desc = Sandbox.const_set(random_test_const_name, ::Class.new(self))
43
+
44
+ if const.is_a?(::Module)
45
+ desc.define_method(:described_class) { const }
46
+ desc.send(:protected, :described_class)
47
+ end
48
+
49
+ desc.instance_eval(&block)
50
+ desc
51
+ end
52
+
53
+ # Add `context` to the DSL.
54
+ singleton_class.send(:alias_method, :context, :describe)
55
+
56
+ # Use the `it` method to define a single spec. A spec should contain one or
57
+ # more expectations that test the state of the code.
58
+ #
59
+ # @example The integer after 41
60
+ # require "r_spec"
61
+ #
62
+ # RSpec.describe Integer do
63
+ # it { expect(41.next).to be 42 }
64
+ # end
65
+ #
66
+ # # Output to the console
67
+ # # Success: expected to be 42.
68
+ #
69
+ # @example A division by zero
70
+ # require "r_spec"
71
+ #
72
+ # RSpec.describe Integer do
73
+ # subject { 41 }
74
+ #
75
+ # it { is_expected.to be_an_instance_of described_class }
76
+ #
77
+ # it "raises an error" do
78
+ # expect { subject / 0 }.to raise_exception ZeroDivisionError
79
+ # end
80
+ # end
81
+ #
82
+ # # Output to the console
83
+ # # Success: expected 41 to be an instance of Integer.
84
+ # # Success: divided by 0.
85
+ #
86
+ # @param _name [String, nil] The name of the spec.
87
+ # @param block [Proc] An expectation to evaluate.
88
+ #
89
+ # @raise (see ExpectationTarget::Base#result)
90
+ # @return (see ExpectationTarget::Base#result)
91
+ def self.it(_name = nil, &block)
92
+ raise ::ArgumentError, "Missing block" unless block
93
+
94
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
95
+
96
+ i = it_example.new
97
+ i.instance_eval(&block)
98
+ end
99
+
100
+ # Use the `its` method to define a single spec that specifies the actual
101
+ # value of an attribute of the subject using `is_expected`.
102
+ #
103
+ # @example The integer after 41
104
+ # require "r_spec"
105
+ #
106
+ # RSpec.describe Integer do
107
+ # subject { 41 }
108
+ #
109
+ # its(:next) { is_expected.to be 42 }
110
+ # end
111
+ #
112
+ # # Output to the console
113
+ # # Success: expected to be 42.
114
+ #
115
+ # @example A division by zero
116
+ # require "r_spec"
117
+ #
118
+ # RSpec.describe Integer do
119
+ # subject { 41 }
120
+ #
121
+ # its(:/, 0) { is_expected.to raise_exception ZeroDivisionError }
122
+ # end
123
+ #
124
+ # # Output to the console
125
+ # # Success: divided by 0.
126
+ #
127
+ # @example A spec without subject
128
+ # require "r_spec"
129
+ #
130
+ # RSpec.describe Integer do
131
+ # its(:boom) { is_expected.to raise_exception RSpec::Error::UndefinedSubject }
132
+ # end
133
+ #
134
+ # # Output to the console
135
+ # # Success: subject not explicitly defined.
136
+ #
137
+ # @param attribute [String, Symbol] The property to call to subject.
138
+ # @param args [Array] An optional list of arguments.
139
+ # @param kwargs [Hash] An optional list of keyword arguments.
140
+ # @param block [Proc] An expectation to evaluate.
141
+ #
142
+ # @raise (see ExpectationTarget::Base#result)
143
+ # @return (see ExpectationTarget::Base#result)
144
+ def self.its(attribute, *args, **kwargs, &block)
145
+ raise ::ArgumentError, "Missing block" unless block
146
+
147
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
148
+
149
+ i = its_example.new
150
+
151
+ i.define_singleton_method(:actual) do
152
+ subject.public_send(attribute, *args, **kwargs)
153
+ end
154
+
155
+ i.instance_eval(&block)
156
+ end
157
+
158
+ # @private
159
+ #
160
+ # @return [Class<Dsl>] The class of the example to be tested.
161
+ def self.it_example
162
+ ::Class.new(self) { include ExpectationHelper::It }
163
+ end
164
+
165
+ # @private
166
+ #
167
+ # @return [Class<Dsl>] The class of the example to be tested.
168
+ def self.its_example
169
+ ::Class.new(self) { include ExpectationHelper::Its }
170
+ end
171
+
172
+ # @private
173
+ #
174
+ # @return [String] A random constant name for a test class.
175
+ def self.random_test_const_name
176
+ "Test#{::SecureRandom.hex(4).to_i(16)}"
177
+ end
178
+
179
+ private_class_method :it_example, :its_example, :random_test_const_name
180
+
181
+ protected
182
+
183
+ def described_class
184
+ raise Error::UndefinedDescribedClass,
185
+ "the first argument to at least one example group must be a module"
186
+ end
187
+
188
+ def subject
189
+ raise Error::UndefinedSubject, "subject not explicitly defined"
190
+ end
191
+ end
192
+ end
193
+
194
+ require_relative "error"
195
+ require_relative "expectation_helper"
196
+ require_relative "sandbox"