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 +4 -4
- data/README.md +183 -35
- data/lib/r_spec.rb +58 -8
- data/lib/r_spec/console.rb +38 -0
- data/lib/r_spec/dsl.rb +300 -69
- data/lib/r_spec/error.rb +14 -0
- data/lib/r_spec/error/pending_expectation.rb +28 -0
- data/lib/r_spec/error/reserved_method.rb +11 -0
- data/lib/r_spec/error/undefined_described_class.rb +11 -0
- data/lib/r_spec/error/undefined_subject.rb +11 -0
- data/lib/r_spec/expectation_helper.rb +10 -0
- data/lib/r_spec/expectation_helper/it.rb +41 -0
- data/lib/r_spec/expectation_helper/its.rb +25 -0
- data/lib/r_spec/expectation_helper/shared.rb +82 -0
- data/lib/r_spec/expectation_target.rb +19 -82
- data/lib/r_spec/expectation_target/base.rb +82 -0
- data/lib/r_spec/expectation_target/block.rb +51 -0
- data/lib/r_spec/expectation_target/value.rb +44 -0
- metadata +35 -10
- data/lib/r_spec/log.rb +0 -24
- data/lib/r_spec/pending.rb +0 -24
- data/lib/r_spec/test.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46557ee64f6cc554942399aa9219e9102879b2b7b113e53dc4ba63d2c0c5c0af
|
4
|
+
data.tar.gz: a83b773e20cf2db46b28586be6836d7913a4c3b8f9bf2ce747110f2556737374
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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)
|
3
|
+
A minimalist __[RSpec](https://github.com/rspec/rspec) clone__ with all the essentials.
|
4
4
|
|
5
|
-

|
6
6
|
|
7
7
|
## Status
|
8
8
|
|
9
9
|
[](https://badge.fury.io/rb/r_spec)
|
10
10
|
[](https://travis-ci.org/cyril/r_spec.rb)
|
11
11
|
[](https://inch-ci.org/github/cyril/r_spec.rb)
|
12
|
+
[](https://rubydoc.info/gems/r_spec/frames)
|
12
13
|
|
13
|
-
##
|
14
|
+
## Project Goals
|
14
15
|
|
15
|
-
|
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
|
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
|
-
*
|
23
|
-
* Built-in matchers do not trust _actual_ and do not send it
|
24
|
-
*
|
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.
|
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
|
+
[](https://asciinema.org/a/418672?autoplay=1)
|
78
|
+
|
58
79
|
## Usage
|
59
80
|
|
60
|
-
|
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
|
-
#
|
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
|
-
|
123
|
+
#### Identity
|
66
124
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
73
|
-
subject do
|
74
|
-
@elements.count
|
75
|
-
end
|
130
|
+
#### Regular expressions
|
76
131
|
|
77
|
-
|
132
|
+
```ruby
|
133
|
+
expect(actual).to match(expected) # passes if expected.match?(actual)
|
134
|
+
```
|
78
135
|
|
79
|
-
|
80
|
-
before do
|
81
|
-
@elements << 1
|
82
|
-
end
|
136
|
+
#### Expecting errors
|
83
137
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
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
|
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
|
-
|
97
|
-
array_spec.rb:22 Success: expected to be 1.
|
221
|
+
## Performance
|
98
222
|
|
99
|
-
|
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/
|
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
|
+
[](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
|
-
#
|
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
|
-
|
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
|
-
|
4
|
-
|
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
|
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(
|
45
|
+
define_method(BEFORE_METHOD) do
|
12
46
|
super()
|
13
|
-
|
47
|
+
instance_eval(&block)
|
14
48
|
end
|
49
|
+
|
50
|
+
private BEFORE_METHOD
|
15
51
|
end
|
16
52
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
20
|
-
|
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
|
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
|
-
#
|
133
|
+
# Defines an example group that describes a unit to be tested.
|
30
134
|
#
|
31
|
-
# @
|
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 =
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
59
|
-
|
231
|
+
exit false
|
232
|
+
ensure
|
233
|
+
example.send(AFTER_METHOD)
|
60
234
|
end
|
61
235
|
|
62
|
-
#
|
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
|
-
# @
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
292
|
+
example.instance_eval(&block)
|
293
|
+
rescue ::SystemExit
|
294
|
+
Console.source(*block.source_location)
|
90
295
|
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
#
|
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
|
-
#
|
105
|
-
|
106
|
-
|
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"
|