r_spec 0.3.3 → 1.0.0.beta5

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