r_spec 0.3.2 → 1.0.0.beta4

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: df8a4849032bcc19682a2f22d851d60cf5ee447822f9f72763eeb8ae56d7f142
4
- data.tar.gz: 2ecfff4ecfc927109e347ec312da8d29c1d0c62718e212ed2262be661b1d5122
3
+ metadata.gz: 0d94ad6d7ba01dc04c4bcbe727543524f6a3d83cf69549a34453c1eefc5282e9
4
+ data.tar.gz: 61b3ebe1299e69d7ef1f2c180325e85e1bda05e2347176ae507fc7430cf311ca
5
5
  SHA512:
6
- metadata.gz: eba24c64920a278f78c5baf29ac41027558a5b0b7e4dfb4ee25e04d7a8920a697fd094c8c829024f5b925a6f9c5485f6ddb3da6411a2c14d683f3299e4828b60
7
- data.tar.gz: 928a480b07ff99162267d5937a0c7be23d35ad6731e1572c9a85c4e7ad0e924c3950c75c03e658574d446bc10a55efdd7e9f47b5336417e97801254cd5fc7d5e
6
+ metadata.gz: 5d44ce99f58c17711be4b3f76cdfaf3c9923d031aa9a7d214db50b990825673b9bb485ce095195594fbeb45977763dc5a0be31770f089cd17dcb1e3ef1d6885b
7
+ data.tar.gz: ca64a27f7cf4032b6b073b9fc2eb05aa37654c02a58289c41094e9afa000deccc3886d03d2043642a02e38cf10b6cc0fbd775e7ef9bc1c29cb592b813dcf5d04
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,131 +1,239 @@
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
- [![Dependency Status](https://gemnasium.com/cyril/r_spec.svg)][gemnasium]
6
- [![Gem Version](https://badge.fury.io/rb/r_spec.svg)][gem]
7
- [![Inline docs](http://inch-ci.org/github/cyril/r_spec.svg?branch=master)][inchpages]
8
- [![Documentation](http://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
3
+ A minimalist __[RSpec](https://github.com/rspec/rspec) clone__ with all the essentials.
9
4
 
10
- > 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)
11
6
 
12
- ***
7
+ ## Status
13
8
 
14
- :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)
15
13
 
16
- 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
17
15
 
18
- 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.
19
17
 
20
- Also, while both gems define an `RSpec` module, **r_spec** (which follows [the gem naming convention](http://guides.rubygems.org/name-your-gem/#use-underscores-for-multiple-words)) is still quite different than **rspec** due to its [Ruby Fix](http://fixrb.org/) dependency.
18
+ ## Some differences
21
19
 
22
- ***
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
+ * The `subject` must be explicitly defined, otherwise it is not implemented.
26
+ * Expectations cannot be added inside a `before` block.
27
+ * [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not exposed to examples.
28
+ * The `let` method defines a helper method rather than a memoized helper method.
29
+ * The one-liner `is_expected` syntax also works with block expectations.
23
30
 
24
- ## Contact
31
+ ## Important ⚠️
25
32
 
26
- * Home page: https://github.com/cyril/r_spec
27
- * Bugs/issues: https://github.com/cyril/r_spec/issues
33
+ To avoid confusion in the community, please note that:
28
34
 
29
- ## Rubies
35
+ - the gem of this project is **not [`rspec`](https://rubygems.org/gems/rspec)**,
36
+ it is **[`r_spec`](https://rubygems.org/gems/r_spec)**;
37
+ - this project is totally independent of [rspec.info](https://rspec.info/).
30
38
 
31
- * [MRI](https://www.ruby-lang.org/)
32
- * [Rubinius](http://rubini.us/)
33
- * [JRuby](http://jruby.org/)
39
+ ### Note
34
40
 
35
- ## Terminal sessions
41
+ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-gem/#use-underscores-for-multiple-words), the module name for this project is `RSpec`.
36
42
 
37
- A comparison between the behavior of a small fix-based script (which became the `r_spec` gem) and `rspec`.
43
+ ## Installation
38
44
 
39
- 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.
45
+ Add this line to your application's Gemfile:
40
46
 
41
- [![What did you RSpec?](https://asciinema.org/a/29070.png)](https://asciinema.org/a/29070)
47
+ ```ruby
48
+ gem "r_spec", ">= 1.0.0.beta4"
49
+ ```
42
50
 
43
- ***
51
+ And then execute:
44
52
 
45
- Unbelievable but true, sometimes "💩" is equal to 42.
53
+ ```sh
54
+ bundle
55
+ ```
46
56
 
47
- Although fun, this _feature_ can be fixed pretty easily...
57
+ Or install it yourself as:
48
58
 
49
- [![Fix RSpec!](https://asciinema.org/a/29172.png)](https://asciinema.org/a/29172)
59
+ ```sh
60
+ gem install r_spec --pre
61
+ ```
50
62
 
51
- ## Installation
63
+ ## Overview
52
64
 
53
- Add this line to your application's Gemfile:
65
+ __RSpec clone__ provides a structure for writing executable examples of how your code should behave.
66
+
67
+ 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.
68
+
69
+ A basic spec looks something like this:
70
+
71
+ [![Super DRY example](https://asciinema.org/a/418672.svg)](https://asciinema.org/a/418672?autoplay=1)
72
+
73
+ ## Usage
74
+
75
+ ### Anatomy of a spec file
76
+
77
+ To use the `RSpec` module and its DSL, you need to add `require "r_spec"` to your spec files.
78
+ Many projects use a custom spec helper which organizes these includes.
79
+
80
+ Concrete test cases are defined in `it` blocks.
81
+ An optional descriptive string states it's purpose and a block contains the main logic performing the test.
82
+
83
+ An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
84
+ Each example can contain multiple expectations, but it should test only one specific behaviour.
85
+
86
+ To express an expectation, wrap an object or block in `expect`, call `to` or `not_to` and pass it a matcher object.
87
+
88
+ If the expectation is met, code execution continues.
89
+ Otherwise the example has _failed_ and other code will not be executed.
90
+
91
+ 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.
92
+
93
+ In test files, specs are structured by example groups which are defined by `describe` and `context` sections.
94
+ Typically a top level `describe` defines the outer unit (such as a class) to be tested by the spec.
95
+ Further `describe` sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
96
+
97
+ For unit tests, it is recommended to follow the conventions for method names:
98
+
99
+ * outer `describe` is the name of the class, inner `describe` targets methods;
100
+ * instance methods are prefixed with `#`, class methods with `.`.
101
+
102
+ To establish certain contexts - think _empty array_ versus _array with elements_ - the `context` method may be used to communicate this to the reader.
103
+ It has a different name, but behaves exactly like `describe`.
104
+
105
+ `describe` and `context` take an optional description as argument and a block containing the individual specs or nested groupings.
106
+
107
+ ### Expectations
108
+
109
+ Expectations define if the value being tested (_actual_) matches a certain value or specific criteria.
110
+
111
+ #### Equivalence
54
112
 
55
113
  ```ruby
56
- gem 'r_spec'
114
+ expect(actual).to eql(expected) # passes if expected.eql?(actual)
115
+ expect(actual).to eq(expected) # passes if expected.eql?(actual)
57
116
  ```
58
117
 
59
- And then execute:
118
+ #### Identity
60
119
 
61
- $ bundle
120
+ ```ruby
121
+ expect(actual).to equal(expected) # passes if expected.equal?(actual)
122
+ expect(actual).to be(expected) # passes if expected.equal?(actual)
123
+ ```
62
124
 
63
- Or install it yourself as:
125
+ #### Regular expressions
64
126
 
65
- $ gem install r_spec
127
+ ```ruby
128
+ expect(actual).to match(expected) # passes if expected.match?(actual)
129
+ ```
66
130
 
67
- ## Usage
131
+ #### Expecting errors
68
132
 
69
- Given this `greeting_spec.rb` spec:
133
+ ```ruby
134
+ expect { actual }.to raise_exception(expected) # passes if expected exception is raised
135
+ ```
136
+
137
+ #### Truth
70
138
 
71
139
  ```ruby
72
- require 'r_spec'
140
+ expect(actual).to be_true # passes if true.equal?(actual)
141
+ ```
142
+
143
+ #### Untruth
73
144
 
74
- greeting = 'Hello, world!'
145
+ ```ruby
146
+ expect(actual).to be_false # passes if false.equal?(actual)
147
+ ```
75
148
 
76
- RSpec.describe 'Test' do
77
- context 'Alice' do
78
- before { greeting.gsub!('world', 'Alice') }
79
- it { expect(greeting).to eql 'Hello, Alice!' }
80
- end
149
+ #### Nil
81
150
 
82
- context 'Bob' do
83
- before { greeting.gsub!('world', 'Bob') }
84
- it { expect(greeting).to eql 'Hello, Bob!' }
85
- end
86
- end
151
+ ```ruby
152
+ expect(actual).to be_nil # passes if nil.equal?(actual)
87
153
  ```
88
154
 
89
- It can be tested in the console with the command:
155
+ #### Type/class
90
156
 
91
- $ ruby greeting_spec.rb
92
- ..
157
+ ```ruby
158
+ expect(actual).to be_instance_of(expected) # passes if expected.equal?(actual.class)
159
+ expect(actual).to be_an_instance_of(expected) # passes if expected.equal?(actual.class)
160
+ ```
161
+
162
+ ### Running specs
163
+
164
+ By convention, specs live in the `spec/` directory of a project. Spec files should end with `_spec.rb` to be recognizable as such.
93
165
 
94
- Ran 2 tests in 0.010994 seconds
95
- 100% compliant - 0 infos, 0 failures, 0 errors
166
+ 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).
167
+ A single file can also be executed directly with the Ruby interpreter.
96
168
 
97
- ## Security
169
+ #### Examples
98
170
 
99
- As a basic form of security __R Spec__ provides a set of SHA512 checksums for
100
- every Gem release. These checksums can be found in the `checksum/` directory.
101
- Although these checksums do not prevent malicious users from tampering with a
102
- built Gem they can be used for basic integrity verification purposes.
171
+ Run all specs in files matching `spec/**/*_spec.rb`:
172
+
173
+ ```sh
174
+ bundle exec rake spec
175
+ ```
103
176
 
104
- The checksum of a file can be checked using the `sha512sum` command. For
105
- example:
177
+ Run a single file:
106
178
 
107
- $ sha512sum pkg/r_spec-0.1.0.gem
108
- e9e35e1953104e2d428b0f217e418db3c1baecd9e011b2545f9fcba4ff7e3bba674c6b928b3d8db842a139cd7cc9806d77ebdc7f710ece4f2aecb343703e2451 pkg/r_spec-0.1.0.gem
179
+ ```sh
180
+ ruby spec/my/test/file_spec.rb
181
+ ```
182
+
183
+ I know that sounds weird, but the [`rspec` command line](https://relishapp.com/rspec/rspec-core/docs/command-line) is also working pretty well:
184
+
185
+ ```sh
186
+ rspec spec/my/test/file_spec.rb
187
+ rspec spec/my/test/file_spec.rb:42
188
+ rspec spec/my/test/
189
+ rspec
190
+ ```
191
+
192
+ ### Spec helper
193
+
194
+ Many projects use a custom spec helper file, usually named `spec/spec_helper.rb`.
195
+
196
+ This file is used to require `r_spec` and other includes, like the code from the project needed for every spec file.
197
+
198
+ ### `rake` integration example
199
+
200
+ The following `Rakefile` settings should be enough:
201
+
202
+ ```ruby
203
+ require "bundler/gem_tasks"
204
+ require "rake/testtask"
205
+
206
+ Rake::TestTask.new do |t|
207
+ t.pattern = "spec/**/*_spec.rb"
208
+ t.verbose = true
209
+ t.warning = true
210
+ end
211
+
212
+ task spec: :test
213
+ task default: :test
214
+ ```
215
+
216
+ ## Test suite
217
+
218
+ __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
219
+
220
+ ## Contact
221
+
222
+ * Home page: https://r-spec.dev
223
+ * Source code: https://github.com/cyril/r_spec.rb
224
+ * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
109
225
 
110
226
  ## Versioning
111
227
 
112
- __R Spec__ follows [Semantic Versioning 2.0](http://semver.org/).
228
+ __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
113
229
 
114
- ## Contributing
230
+ ## Special thanks
115
231
 
116
- 1. [Fork it](https://github.com/cyril/r_spec/fork)
117
- 2. Create your feature branch (`git checkout -b my-new-feature`)
118
- 3. Commit your changes (`git commit -am 'Add some feature'`)
119
- 4. Push to the branch (`git push origin my-new-feature`)
120
- 5. Create a new Pull Request
232
+ I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
233
+ It's a great framework and it's a pleasure to work with every day.
121
234
 
122
- ## License
235
+ Without RSpec, this clone would not have been possible. ❤️
123
236
 
124
- See `LICENSE.md` file.
237
+ ## License
125
238
 
126
- [gem]: https://rubygems.org/gems/r_spec
127
- [travis]: https://travis-ci.org/cyril/r_spec
128
- [codeclimate]: https://codeclimate.com/github/cyril/r_spec
129
- [gemnasium]: https://gemnasium.com/cyril/r_spec
130
- [inchpages]: http://inch-ci.org/github/cyril/r_spec
131
- [rubydoc]: http://rubydoc.info/gems/r_spec/frames
239
+ 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,65 @@
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
+ # @example The basic behavior of arrays
13
+ # require "r_spec"
14
+ #
15
+ # RSpec.describe Array do
16
+ # describe "#size" do
17
+ # it "correctly reports the number of elements in the Array" do
18
+ # expect([1, 2, 3].size).to eq 3
19
+ # end
20
+ # end
21
+ #
22
+ # describe "#empty?" do
23
+ # it "is empty when no elements are in the array" do
24
+ # expect([].empty?).to be_true
25
+ # end
26
+ #
27
+ # it "is not empty if there are elements in the array" do
28
+ # expect([1].empty?).to be_false
29
+ # end
30
+ # end
31
+ # end
32
+ #
33
+ # @example An inherited definition of let
34
+ # require "r_spec"
10
35
  #
36
+ # RSpec.describe Integer do
37
+ # let(:answer) { 42 }
38
+ #
39
+ # it "returns the value" do
40
+ # expect(answer).to be(42)
41
+ # end
42
+ #
43
+ # context "when the number is incremented" do
44
+ # let(:answer) { super().next }
45
+ #
46
+ # it "returns the next value" do
47
+ # expect(answer).to be(43)
48
+ # end
49
+ # end
50
+ # end
51
+ #
52
+ # @api public
11
53
  module RSpec
12
54
  # Specs are built with this method.
13
55
  #
14
- # @example The answer must be equal to 42.
15
- # describe('the answer') do
16
- # it { expect(42).to be 42 }
17
- # end
18
- #
19
- # @param front_object [#object_id] The front object.
20
- # @param options [Hash] Some options.
21
- # @param specs [Proc] The set of specs.
56
+ # @param const [Module, String] A module to include in block context.
57
+ # @param block [Proc] The block to define the specs.
22
58
  #
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?
59
+ # @api public
60
+ def self.describe(const = nil, &block)
61
+ Dsl.describe(const, &block)
29
62
  end
30
63
  end
31
64
 
32
- require_relative File.join 'fix', 'it'
33
- require_relative File.join 'fix', 'on'
65
+ require_relative File.join("r_spec", "dsl")
data/lib/r_spec/dsl.rb ADDED
@@ -0,0 +1,135 @@
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
+ # Define a single spec. A spec should contain one or more expectations that
57
+ # test the state of the code.
58
+ #
59
+ # @param block [Proc] An expectation to evaluate.
60
+ #
61
+ # @raise (see ExpectationTarget::Base#result)
62
+ # @return (see ExpectationTarget::Base#result)
63
+ def self.it(_name = nil, &block)
64
+ raise ::ArgumentError, "Missing block" unless block
65
+
66
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
67
+
68
+ i = it_example.new
69
+ i.instance_eval(&block)
70
+ end
71
+
72
+ # Define a single spec. A spec should contain one or more expectations that
73
+ # test the state of the code.
74
+ #
75
+ # @example The value after 41
76
+ # require "r_spec"
77
+ #
78
+ # RSpec.describe Integer do
79
+ # subject { 41 }
80
+ #
81
+ # its(:next) { is_expected.to be 42 }
82
+ # end
83
+ #
84
+ # @example Without defining a subject
85
+ # require "r_spec"
86
+ #
87
+ # RSpec.describe Integer do
88
+ # its(:next) { is_expected.to raise_exception NameError }
89
+ # end
90
+ #
91
+ # @param attribute [String, Symbol] The property to call to subject.
92
+ #
93
+ # @raise (see ExpectationTarget::Base#result)
94
+ # @return (see ExpectationTarget::Base#result)
95
+ def self.its(attribute, *args, **kwargs, &block)
96
+ raise ::ArgumentError, "Missing block" unless block
97
+
98
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
99
+
100
+ i = its_example.new
101
+
102
+ i.define_singleton_method(:actual) do
103
+ subject.public_send(attribute, *args, **kwargs)
104
+ end
105
+
106
+ i.instance_eval(&block)
107
+ end
108
+
109
+ # @private
110
+ #
111
+ # @return [Class<Dsl>] The class of the example to be tested.
112
+ def self.it_example
113
+ ::Class.new(self) { include ExpectationHelper::It }
114
+ end
115
+
116
+ # @private
117
+ #
118
+ # @return [Class<Dsl>] The class of the example to be tested.
119
+ def self.its_example
120
+ ::Class.new(self) { include ExpectationHelper::Its }
121
+ end
122
+
123
+ # @private
124
+ #
125
+ # @return [String] A random constant name for a test class.
126
+ def self.random_test_const_name
127
+ "Test#{::SecureRandom.hex(4).to_i(16)}"
128
+ end
129
+
130
+ private_class_method :it_example, :its_example, :random_test_const_name
131
+ end
132
+ end
133
+
134
+ require_relative "expectation_helper"
135
+ require_relative "sandbox"