r_spec 1.0.0.beta3 → 1.0.0.beta8
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 +127 -166
- data/lib/r_spec.rb +57 -5
- data/lib/r_spec/console.rb +38 -0
- data/lib/r_spec/dsl.rb +297 -76
- 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 +7 -5
- data/lib/r_spec/expectation_target/base.rb +21 -5
- data/lib/r_spec/expectation_target/block.rb +7 -18
- data/lib/r_spec/expectation_target/value.rb +6 -17
- metadata +19 -11
- 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: 654594b32d5ef5c97f68de1a5f3e99cc38364de1703f747d841fccba725b878c
|
4
|
+
data.tar.gz: 11038ae3f4f56cf40a7e2d99077ce28e7ee239e36d775dcfe8035954df240e07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91f106a5823915d56c59edf1a7da73285fdf05ef38827368cc44c135c2973b03aa4e3fe013a2e423ffd7bfdc2bd3f24ccd208f6f8e11fddeba3e95bfe800d7cb
|
7
|
+
data.tar.gz: e25a4dc18ba5ffb7aabb0bbe42dc9ef6b88756e4fb825fa895683e181a1ac05a61d153da05deb7c5142473704f2174b0a5a4e75dd612ed0481b2402a0c15a350
|
data/README.md
CHANGED
@@ -1,27 +1,34 @@
|
|
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
|
-
[](https://rubydoc.info/gems/r_spec/frames)
|
12
12
|
|
13
|
-
##
|
13
|
+
## Project goals
|
14
14
|
|
15
|
-
|
15
|
+
* Enforce the guidelines and best practices outlined in the community [RSpec style guide](https://rspec.rubystyle.guide/).
|
16
|
+
* Provide most of RSpec's DSL to express expected outcomes of a code example without magic power.
|
16
17
|
|
17
18
|
## Some differences
|
18
19
|
|
19
20
|
* Less features and an implementation with much less code complexity.
|
20
21
|
* Spec files can also be executed directly with the `ruby` executable.
|
21
22
|
* There is no option to activate monkey-patching.
|
22
|
-
*
|
23
|
-
* Built-in matchers do not trust _actual_ and do not send it
|
24
|
-
*
|
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.
|
31
|
+
* `subject`, `before`, `after` and `let` definitions must come before examples.
|
25
32
|
|
26
33
|
## Important ⚠️
|
27
34
|
|
@@ -40,7 +47,7 @@ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-ge
|
|
40
47
|
Add this line to your application's Gemfile:
|
41
48
|
|
42
49
|
```ruby
|
43
|
-
gem "r_spec", ">= 1.0.0.
|
50
|
+
gem "r_spec", ">= 1.0.0.beta8"
|
44
51
|
```
|
45
52
|
|
46
53
|
And then execute:
|
@@ -55,222 +62,170 @@ Or install it yourself as:
|
|
55
62
|
gem install r_spec --pre
|
56
63
|
```
|
57
64
|
|
65
|
+
## Overview
|
66
|
+
|
67
|
+
__RSpec clone__ provides a structure for writing executable examples of how your code should behave.
|
68
|
+
|
69
|
+
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.
|
70
|
+
|
71
|
+
A basic spec looks something like this:
|
72
|
+
|
73
|
+
[](https://asciinema.org/a/418672?autoplay=1)
|
74
|
+
|
58
75
|
## Usage
|
59
76
|
|
60
|
-
|
77
|
+
### Anatomy of a spec file
|
61
78
|
|
62
|
-
|
79
|
+
To use the `RSpec` module and its DSL, you need to add `require "r_spec"` to your spec files.
|
80
|
+
Many projects use a custom spec helper which organizes these includes.
|
63
81
|
|
64
|
-
|
82
|
+
Concrete test cases are defined in `it` blocks.
|
83
|
+
An optional descriptive string states it's purpose and a block contains the main logic performing the test.
|
65
84
|
|
66
|
-
|
67
|
-
RSpec.describe String do
|
68
|
-
end
|
69
|
-
```
|
85
|
+
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.
|
70
86
|
|
71
|
-
|
87
|
+
An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
|
88
|
+
Each example can contain multiple expectations, but it should test only one specific behaviour.
|
72
89
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
protected
|
77
|
-
|
78
|
-
def described_class
|
79
|
-
String
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
```
|
90
|
+
To express an expectation, wrap an object or block in `expect`, call `to` (or `not_to`) and pass it a matcher object.
|
91
|
+
If the expectation is met, code execution continues.
|
92
|
+
Otherwise the example has _failed_ and other code will not be executed.
|
84
93
|
|
85
|
-
|
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).
|
86
97
|
|
87
|
-
|
98
|
+
For unit tests, it is recommended to follow the conventions for method names:
|
88
99
|
|
89
|
-
|
100
|
+
* outer `describe` is the name of the class, inner `describe` targets methods;
|
101
|
+
* instance methods are prefixed with `#`, class methods with `.`.
|
90
102
|
|
91
|
-
|
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`.
|
92
105
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
:foo
|
97
|
-
end
|
98
|
-
end
|
99
|
-
```
|
106
|
+
`describe` and `context` take an optional description as argument and a block containing the individual specs or nested groupings.
|
107
|
+
|
108
|
+
### Expectations
|
100
109
|
|
101
|
-
|
110
|
+
Expectations define if the value being tested (_actual_) matches a certain value or specific criteria.
|
111
|
+
|
112
|
+
#### Equivalence
|
102
113
|
|
103
114
|
```ruby
|
104
|
-
|
105
|
-
|
106
|
-
protected
|
107
|
-
|
108
|
-
def subject
|
109
|
-
:foo
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
115
|
+
expect(actual).to eql(expected) # passes if expected.eql?(actual)
|
116
|
+
expect(actual).to eq(expected) # passes if expected.eql?(actual)
|
113
117
|
```
|
114
118
|
|
115
|
-
|
116
|
-
|
117
|
-
Example of specification content:
|
119
|
+
#### Identity
|
118
120
|
|
119
121
|
```ruby
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
describe "Embedded describe" do
|
124
|
-
# embedded describe block
|
125
|
-
end
|
126
|
-
end
|
122
|
+
expect(actual).to equal(expected) # passes if expected.equal?(actual)
|
123
|
+
expect(actual).to be(expected) # passes if expected.equal?(actual)
|
127
124
|
```
|
128
125
|
|
129
|
-
|
126
|
+
#### Regular expressions
|
130
127
|
|
131
128
|
```ruby
|
132
|
-
|
133
|
-
class Test3582143298
|
134
|
-
# main describe block
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
module RSpec::Test
|
139
|
-
class Test198623541 < RSpec::Test::Test3582143298
|
140
|
-
# embedded describe block
|
141
|
-
end
|
142
|
-
end
|
129
|
+
expect(actual).to match(expected) # passes if expected.match?(actual)
|
143
130
|
```
|
144
131
|
|
145
|
-
|
146
|
-
|
147
|
-
Example of specification content:
|
132
|
+
#### Expecting errors
|
148
133
|
|
149
134
|
```ruby
|
150
|
-
|
151
|
-
let(:var0) { 42 }
|
152
|
-
let(:var1) { 42 + var3 }
|
153
|
-
let(:var3) { 42 }
|
154
|
-
end
|
135
|
+
expect { actual }.to raise_exception(expected) # passes if expected exception is raised
|
155
136
|
```
|
156
137
|
|
157
|
-
|
138
|
+
#### Truth
|
158
139
|
|
159
140
|
```ruby
|
160
|
-
|
161
|
-
class Test3582143298
|
162
|
-
protected
|
163
|
-
|
164
|
-
def var0
|
165
|
-
42
|
166
|
-
end
|
167
|
-
|
168
|
-
def var1
|
169
|
-
42 + var3
|
170
|
-
end
|
171
|
-
|
172
|
-
def var3
|
173
|
-
42
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
141
|
+
expect(actual).to be_true # passes if true.equal?(actual)
|
177
142
|
```
|
178
143
|
|
179
|
-
|
180
|
-
|
181
|
-
Example of specification content:
|
144
|
+
#### Untruth
|
182
145
|
|
183
146
|
```ruby
|
184
|
-
|
185
|
-
before do
|
186
|
-
puts "hello"
|
187
|
-
end
|
188
|
-
end
|
147
|
+
expect(actual).to be_false # passes if false.equal?(actual)
|
189
148
|
```
|
190
149
|
|
191
|
-
|
150
|
+
#### Nil
|
192
151
|
|
193
152
|
```ruby
|
194
|
-
|
195
|
-
class Test3582143298
|
196
|
-
def initialize
|
197
|
-
puts "hello"
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
153
|
+
expect(actual).to be_nil # passes if nil.equal?(actual)
|
201
154
|
```
|
202
155
|
|
203
|
-
|
204
|
-
|
205
|
-
Example of specification content:
|
156
|
+
#### Type/class
|
206
157
|
|
207
158
|
```ruby
|
208
|
-
|
209
|
-
|
210
|
-
end
|
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)
|
211
161
|
```
|
212
162
|
|
213
|
-
|
163
|
+
### Running specs
|
214
164
|
|
215
|
-
|
216
|
-
module RSpec::Test
|
217
|
-
class Test3582143298
|
218
|
-
end
|
219
|
-
end
|
165
|
+
By convention, specs live in the `spec/` directory of a project. Spec files should end with `_spec.rb` to be recognizable as such.
|
220
166
|
|
221
|
-
|
222
|
-
|
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.
|
223
169
|
|
224
|
-
|
225
|
-
end
|
170
|
+
#### Examples
|
226
171
|
|
227
|
-
|
228
|
-
|
229
|
-
|
172
|
+
Run all specs in files matching `spec/**/*_spec.rb`:
|
173
|
+
|
174
|
+
```sh
|
175
|
+
bundle exec rake spec
|
230
176
|
```
|
231
177
|
|
232
|
-
|
178
|
+
Run a single file:
|
233
179
|
|
234
|
-
|
180
|
+
```sh
|
181
|
+
ruby spec/my/test/file_spec.rb
|
182
|
+
```
|
235
183
|
|
236
|
-
|
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:
|
237
185
|
|
238
|
-
```
|
239
|
-
|
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
|
+
```
|
240
192
|
|
241
|
-
|
193
|
+
### Spec helper
|
242
194
|
|
243
|
-
|
244
|
-
before do
|
245
|
-
@elements = described_class.new
|
246
|
-
end
|
195
|
+
Many projects use a custom spec helper file, usually named `spec/spec_helper.rb`.
|
247
196
|
|
248
|
-
|
249
|
-
subject do
|
250
|
-
@elements.count
|
251
|
-
end
|
197
|
+
This file is used to require `r_spec` and other includes, like the code from the project needed for every spec file.
|
252
198
|
|
253
|
-
|
199
|
+
### `rake` integration example
|
254
200
|
|
255
|
-
|
256
|
-
before do
|
257
|
-
@elements << 1
|
258
|
-
end
|
201
|
+
The following `Rakefile` settings should be enough:
|
259
202
|
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
263
211
|
end
|
212
|
+
|
213
|
+
task spec: :test
|
214
|
+
task default: :test
|
264
215
|
```
|
265
216
|
|
266
|
-
|
217
|
+
## Performance
|
267
218
|
|
268
|
-
|
269
|
-
ruby array_spec.rb
|
270
|
-
```
|
219
|
+
### Runtime
|
271
220
|
|
272
|
-
|
273
|
-
|
221
|
+
Benchmark against [100 executions of a file containing one expectation](https://github.com/cyril/r_spec.rb/blob/main/benchmark/) (lower is better).
|
222
|
+
|
223
|
+
| Framework | Seconds to complete |
|
224
|
+
|-------------|---------------------|
|
225
|
+
| `r_spec` | 13.0 |
|
226
|
+
| `rspec` | 32.2 |
|
227
|
+
| `minitest` | 17.5 |
|
228
|
+
| `test-unit` | 20.5 |
|
274
229
|
|
275
230
|
## Test suite
|
276
231
|
|
@@ -282,16 +237,22 @@ __RSpec clone__'s specifications are self-described here: [spec/](https://github
|
|
282
237
|
* Source code: https://github.com/cyril/r_spec.rb
|
283
238
|
* Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
|
284
239
|
|
285
|
-
##
|
286
|
-
|
287
|
-
__RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
|
288
|
-
|
289
|
-
## Special thanks
|
240
|
+
## Special thanks ❤️
|
290
241
|
|
291
242
|
I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
|
292
243
|
It's a great framework and it's a pleasure to work with every day.
|
293
244
|
|
294
|
-
Without RSpec, this clone would not have been possible.
|
245
|
+
Without RSpec, this clone would not have been possible.
|
246
|
+
|
247
|
+
## Buy me a coffee ☕
|
248
|
+
|
249
|
+
If you like this project please consider making a small donation.
|
250
|
+
|
251
|
+
[](https://etherscan.io/address/0x834b5c1feaff5aebf9cd0f25dc38e741d65ab773)
|
252
|
+
|
253
|
+
## Versioning
|
254
|
+
|
255
|
+
__RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
|
295
256
|
|
296
257
|
## License
|
297
258
|
|
data/lib/r_spec.rb
CHANGED
@@ -1,14 +1,68 @@
|
|
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.
|
@@ -18,8 +72,6 @@ module RSpec
|
|
18
72
|
#
|
19
73
|
# @api public
|
20
74
|
def self.describe(const, &block)
|
21
|
-
|
75
|
+
Dsl.describe(const, &block)
|
22
76
|
end
|
23
77
|
end
|
24
|
-
|
25
|
-
require_relative File.join("r_spec", "dsl")
|