r_spec-clone 1.0.0.rc1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b96788e870f05feb6035cc289a06d274f8e68f1c6dbda8187db392d2ddf58cc5
4
+ data.tar.gz: cecca9a87c7860b87aa2485846dadb23337a5292082e9d71685ed3bb61820968
5
+ SHA512:
6
+ metadata.gz: aedab54edd51616dcb01f83ffe82e68285d3d3585c51cb3dc60c78c8d16230f3a5f78e9a886d678153cab84a6aa2e59b17d4d6b7cdb2301bb4ee3ba7687a8290
7
+ data.tar.gz: f2654fab9bb2b84450c69e1ab94fca926b54551c6de307bb7432ece5aedabd8386b661926f65005f02fa4fbd33bd22c8173226ebe6f57001b747a8e1e74905de
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2021 Cyril Kato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,259 @@
1
+ # RSpec clone
2
+
3
+ A minimalist __RSpec clone__ with all the essentials.
4
+
5
+ ![What did you RSpec?](https://github.com/cyril/r_spec-clone.rb/raw/main/img/what-did-you-rspec.jpg)
6
+
7
+ ## Status
8
+
9
+ [![Version](https://img.shields.io/github/v/tag/cyril/r_spec-clone.rb?label=Version&logo=github)](https://github.com/cyril/r_spec-clone.rb/releases)
10
+ [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?)](https://rubydoc.info/github/cyril/r_spec-clone.rb/main)
11
+ [![CI](https://github.com/cyril/r_spec-clone.rb/workflows/CI/badge.svg?branch=main)](https://github.com/cyril/r_spec-clone.rb/actions?query=workflow%3Aci+branch%3Amain)
12
+ [![RuboCop](https://github.com/cyril/r_spec-clone.rb/workflows/RuboCop/badge.svg?branch=main)](https://github.com/cyril/r_spec-clone.rb/actions?query=workflow%3Arubocop+branch%3Amain)
13
+ [![License](https://img.shields.io/github/license/cyril/r_spec-clone.rb?label=License&logo=github)](https://github.com/cyril/r_spec-clone.rb/raw/main/LICENSE.md)
14
+
15
+ ## Project goals
16
+
17
+ 1. Keep a low level of code complexity and ensure thread safety.
18
+ 2. The interface must translate into atomic and simple Ruby objects.
19
+ 3. Avoid overloading the interface with additional alternative syntaxes.
20
+ 4. Provide most of RSpec's DSL to express expected outcomes of a code example.
21
+
22
+ ## Some differences
23
+
24
+ * Spec files can be executed with `ruby` directly.
25
+ * There is no option to activate monkey-patching.
26
+ * It does not rely on hacks such as [`at_exit` hook](https://blog.arkency.com/2013/06/are-we-abusing-at-exit/) to trigger the tests.
27
+ * Built-in matchers [do not trust _actual_](https://asciinema.org/a/29172?autoplay=1&speed=2) and do not send it messages.
28
+ * If no `subject` has been explicitly determined, none is defined.
29
+ * If no described class is set, `described_class` is undefined instead of `nil`.
30
+ * Expectations cannot be added inside a `before` block.
31
+ * [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not exposed to examples.
32
+ * The `let` method defines a helper method rather than a memoized helper method.
33
+ * The one-liner `is_expected` syntax also works with block expectations.
34
+ * `subject`, `before`, `after` and `let` definitions must come before examples.
35
+ * Each [`context` runs its tests in _isolation_](https://asciinema.org/a/29070?autoplay=1&speed=2) to prevent side effects.
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application's Gemfile:
40
+
41
+ ```ruby
42
+ gem "r_spec-clone", ">= 1.0.0.rc1"
43
+ ```
44
+
45
+ And then execute:
46
+
47
+ ```sh
48
+ bundle
49
+ ```
50
+
51
+ Or install it yourself as:
52
+
53
+ ```sh
54
+ gem install r_spec-clone --pre
55
+ ```
56
+
57
+ ## Overview
58
+
59
+ __RSpec clone__ provides a structure for writing executable examples of how your code should behave.
60
+
61
+ Inspired by RSpec, it includes a domain specific language (DSL) that allows you to write examples in a way similar to plain english.
62
+
63
+ A basic spec looks something like this:
64
+
65
+ [![RSpec clone demo](https://asciinema.org/a/422210.svg)](https://asciinema.org/a/422210?autoplay=1&speed=2)
66
+
67
+ ## Usage
68
+
69
+ ### Anatomy of a spec file
70
+
71
+ To use the `RSpec` module and its DSL, you need to add `require "r_spec/clone"` to your spec files.
72
+ Many projects use a custom spec helper which organizes these includes.
73
+
74
+ Concrete test cases are defined in `it` blocks.
75
+ An optional descriptive string states it's purpose and a block contains the main logic performing the test.
76
+
77
+ 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.
78
+
79
+ An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
80
+ Each example can contain multiple expectations, but it should test only one specific behaviour.
81
+
82
+ To express an expectation, wrap an object or block in `expect`, call `to` (or `not_to`) and pass it a matcher object.
83
+ If the expectation is met, code execution continues.
84
+ Otherwise the example has _failed_ and other code will not be executed.
85
+
86
+ In test files, specs are structured by example groups which are defined by `describe` and `context` sections.
87
+ Typically a top level `describe` defines the outer unit (such as a class) to be tested by the spec.
88
+ Further `describe` sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
89
+
90
+ For unit tests, it is recommended to follow the conventions for method names:
91
+
92
+ * outer `describe` is the name of the class, inner `describe` targets methods;
93
+ * instance methods are prefixed with `#`, class methods with `.`.
94
+
95
+ To establish certain contexts — think _empty array_ versus _array with elements_ — the `context` method may be used to communicate this to the reader.
96
+ Its behavior is slightly different from `describe` because each `context` runs its tests in isolation,
97
+ so side effects caused by testing do not propagate out of contexts.
98
+
99
+ ### Expectations
100
+
101
+ Expectations define if the value being tested (_actual_) matches a certain value or specific criteria.
102
+
103
+ #### Equivalence
104
+
105
+ ```ruby
106
+ expect(actual).to eql(expected) # passes if expected.eql?(actual)
107
+ expect(actual).to eq(expected) # passes if expected.eql?(actual)
108
+ ```
109
+
110
+ #### Identity
111
+
112
+ ```ruby
113
+ expect(actual).to equal(expected) # passes if expected.equal?(actual)
114
+ expect(actual).to be(expected) # passes if expected.equal?(actual)
115
+ ```
116
+
117
+ #### Regular expressions
118
+
119
+ ```ruby
120
+ expect(actual).to match(expected) # passes if expected.match?(actual)
121
+ ```
122
+
123
+ #### Expecting errors
124
+
125
+ ```ruby
126
+ expect { actual }.to raise_exception(expected) # passes if expected exception is raised
127
+ ```
128
+
129
+ #### Truth
130
+
131
+ ```ruby
132
+ expect(actual).to be_true # passes if true.equal?(actual)
133
+ ```
134
+
135
+ #### Untruth
136
+
137
+ ```ruby
138
+ expect(actual).to be_false # passes if false.equal?(actual)
139
+ ```
140
+
141
+ #### Nil
142
+
143
+ ```ruby
144
+ expect(actual).to be_nil # passes if nil.equal?(actual)
145
+ ```
146
+
147
+ #### Type/class
148
+
149
+ ```ruby
150
+ expect(actual).to be_instance_of(expected) # passes if expected.equal?(actual.class)
151
+ expect(actual).to be_an_instance_of(expected) # passes if expected.equal?(actual.class)
152
+ ```
153
+
154
+ ### Running specs
155
+
156
+ By convention, specs live in the `spec/` directory of a project. Spec files should end with `_spec.rb` to be recognizable as such.
157
+
158
+ Depending of the project settings, you may run the specs of a project by running `rake spec` (see _Rake integration example_ section below).
159
+ A single file can also be executed directly with the Ruby interpreter.
160
+
161
+ #### Examples
162
+
163
+ Run all specs in files matching `spec/**/*_spec.rb`:
164
+
165
+ ```sh
166
+ bundle exec rake spec
167
+ ```
168
+
169
+ Run a single file:
170
+
171
+ ```sh
172
+ ruby spec/my/test/file_spec.rb
173
+ ```
174
+
175
+ It is not recommended, but the RSpec's [`rspec` command line](https://relishapp.com/rspec/rspec-core/docs/command-line) might also work:
176
+
177
+ ```sh
178
+ rspec spec/my/test/file_spec.rb
179
+ rspec spec/my/test/file_spec.rb:42
180
+ rspec spec/my/test/
181
+ rspec
182
+ ```
183
+
184
+ ### Spec helper
185
+
186
+ Many projects use a custom spec helper file, usually named `spec/spec_helper.rb`.
187
+
188
+ This file is used to require `r_spec/clone` and other includes, like the code from the project needed for every spec file.
189
+
190
+ ### Rake integration example
191
+
192
+ The following `Rakefile` settings should be enough:
193
+
194
+ ```ruby
195
+ require "bundler/gem_tasks"
196
+ require "rake/testtask"
197
+
198
+ Rake::TestTask.new do |t|
199
+ t.pattern = "spec/**/*_spec.rb"
200
+ end
201
+
202
+ task spec: :test
203
+ task default: :test
204
+ ```
205
+
206
+ And then execute:
207
+
208
+ ```sh
209
+ bundle exec rake
210
+ ```
211
+
212
+ ## Performance
213
+
214
+ ### Runtime
215
+
216
+ Benchmark against [100 executions of a file containing one expectation](https://github.com/cyril/r_spec-clone.rb/blob/main/benchmark/) (lower is better).
217
+
218
+ ![Runtime](https://clone.r-spec.dev/benchmark-runtime.png)
219
+
220
+ ## Test suite
221
+
222
+ __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec-clone.rb/blob/main/spec/)
223
+
224
+ ## Contact
225
+
226
+ * Home page: [https://r-spec.dev/](https://r-spec.dev/)
227
+ * Cheatsheet: [https://clone.r-spec.dev/cheatsheet.html](https://clone.r-spec.dev/cheatsheet.html)
228
+ * Source code: [https://github.com/cyril/r_spec-clone.rb](https://github.com/cyril/r_spec-clone.rb)
229
+ * API Doc: [https://rubydoc.info/gems/r_spec-clone](https://rubydoc.info/gems/r_spec-clone)
230
+ * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
231
+
232
+ ## Special thanks ❤️
233
+
234
+ I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
235
+ It's a great framework and it's a pleasure to work with every day.
236
+
237
+ Without RSpec, this clone would not have been possible.
238
+
239
+ ## Buy me a coffee ☕
240
+
241
+ If you like this project, please consider making a small donation to Batman.
242
+
243
+ [![Donate](https://img.shields.io/badge/Donate-batman.eth-purple.svg)](https://etherscan.io/address/batman.eth)
244
+
245
+ ## Versioning
246
+
247
+ __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
248
+
249
+ ## License
250
+
251
+ The [gem](https://rubygems.org/gems/r_spec-clone) is available as open source under the terms of the [MIT License](https://github.com/cyril/r_spec-clone.rb/raw/main/LICENSE.md).
252
+
253
+ ## One more thing
254
+
255
+ Under the hood, __RSpec clone__ is largely animated by [a collection of testing libraries designed to make programmers happy](https://github.com/fixrb/).
256
+
257
+ It's a living example of what we can do combining small libraries together that can boost the fun of programming.
258
+
259
+ ![Fix testing tools logo for Ruby](https://github.com/cyril/r_spec-clone.rb/raw/main/img/fixrb.svg)
data/lib/r_spec.rb ADDED
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("r_spec", "clone", "dsl")
4
+
5
+ # Top level namespace for the RSpec clone.
6
+ #
7
+ # @example The true from the false
8
+ # require "r_spec/clone"
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/clone"
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
44
+ # require "r_spec/clone"
45
+ #
46
+ # RSpec.describe Integer do
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
60
+ # end
61
+ #
62
+ # # Output to the console
63
+ # # Success: expected to be 42.
64
+ # # Success: expected to be 43.
65
+ #
66
+ # @api public
67
+ module RSpec
68
+ # Defines an example group that establishes a specific context, like _empty
69
+ # array_ versus _array with elements_.
70
+ #
71
+ # Unlike {.describe}, the block is evaluated in isolation in order to scope
72
+ # possible side effects inside its context.
73
+ #
74
+ # @example
75
+ # require "r_spec/clone"
76
+ #
77
+ # RSpec.context "when divided by zero" do
78
+ # subject { 42 / 0 }
79
+ #
80
+ # it { is_expected.to raise_exception ZeroDivisionError }
81
+ # end
82
+ #
83
+ # # Output to the console
84
+ # # Success: divided by 0.
85
+ #
86
+ # @param description [String] A description that usually begins with "when",
87
+ # "with" or "without".
88
+ # @param block [Proc] The block to define the specs.
89
+ #
90
+ # @api public
91
+ def self.context(description, &block)
92
+ Clone::Dsl.context(description, &block)
93
+ end
94
+
95
+ # Defines an example group that describes a unit to be tested.
96
+ #
97
+ # @example
98
+ # require "r_spec/clone"
99
+ #
100
+ # RSpec.describe String do
101
+ # describe "+" do
102
+ # it("concats") { expect("foo" + "bar").to eq "foobar" }
103
+ # end
104
+ # end
105
+ #
106
+ # # Output to the console
107
+ # # Success: expected to eq "foobar".
108
+ #
109
+ # @param const [Module, String] A module to include in block context.
110
+ # @param block [Proc] The block to define the specs.
111
+ #
112
+ # @api public
113
+ def self.describe(const, &block)
114
+ Clone::Dsl.describe(const, &block)
115
+ end
116
+
117
+ # Defines a concrete test case.
118
+ #
119
+ # The test is performed by the block supplied to &block.
120
+ #
121
+ # @example The integer after 41
122
+ # require "r_spec/clone"
123
+ #
124
+ # RSpec.it { expect(41.next).to be 42 }
125
+ #
126
+ # # Output to the console
127
+ # # Success: expected to be 42.
128
+ #
129
+ # It is usually used inside a {Clone::Dsl.describe} or {Clone::Dsl.context}
130
+ # section.
131
+ #
132
+ # @param name [String, nil] The name of the spec.
133
+ # @param block [Proc] An expectation to evaluate.
134
+ #
135
+ # @raise (see RSpec::ExpectationTarget::Base#result)
136
+ # @return (see RSpec::ExpectationTarget::Base#result)
137
+ #
138
+ # @api public
139
+ def self.it(name = nil, &block)
140
+ Clone::Dsl.it(name, &block)
141
+ end
142
+
143
+ # Defines a pending test case.
144
+ #
145
+ # `&block` is never evaluated. It can be used to describe behaviour that is
146
+ # not yet implemented.
147
+ #
148
+ # @example
149
+ # require "r_spec/clone"
150
+ #
151
+ # RSpec.pending "is implemented but waiting" do
152
+ # expect something to be finished
153
+ # end
154
+ #
155
+ # RSpec.pending "is not yet implemented and waiting"
156
+ #
157
+ # # Output to the console
158
+ # # Warning: is implemented but waiting.
159
+ # # Warning: is not yet implemented and waiting.
160
+ #
161
+ # It is usually used inside a {Clone::Dsl.describe} or {Clone::Dsl.context}
162
+ # section.
163
+ #
164
+ # @param message [String] The reason why the example is pending.
165
+ #
166
+ # @return [nil] Write a message to STDOUT.
167
+ #
168
+ # @api public
169
+ def self.pending(message)
170
+ Clone::Dsl.pending(message)
171
+ end
172
+ end