RSpec clone 
A minimalist RSpec clone with all the essentials.

Status
Project Goals
- Maintain low code complexity to avoid false negatives and false positives.
 - Implement the loading of specifications using simple, atomic, and thread-safe Ruby primitives.
 - Avoid cluttering the interface with unnecessary alternative syntaxes.
 - Provide the basics of the RSpec DSL for writing tests.
 
Some Differences
- Monkey-patching is not an available option.
 - The framework does not use hacks such as the 
at_exithook to trigger tests. - Malicious actual values cannot compromise results.
 - If no 
subjectis explicitly determined, it remains undefined. - If no described class is set, 
described_classis undefined rather thannil. - Expectations cannot be added inside a 
beforeblock. - Arbitrary helper methods are not accessible within examples.
 - The 
letmethod defines a helper method rather than a memoized helper method. - The one-liner 
is_expectedsyntax is compatible with block expectations. - Definitions of 
subject,before, andletmust precede examples. - The 
afterhook is unsupported. - The execution of the test suite halts immediately when an error is detected.
 - Each 
contextblock isolates its tests and any potential side effects. - The 
itsmethod is available without the need for external dependencies. 
Installation
Add this line to your application’s Gemfile:
gem "r_spec-clone"
And then execute:
bundle install
Or install it yourself as:
gem install r_spec-clone
Overview
RSpec clone provides a structure for writing executable examples of how your code should behave.
Inspired by RSpec, it includes a domain-specific language (DSL) that allows you to write examples in a way similar to plain english.
A basic spec looks something like this:
Usage
Anatomy of a Spec File
To utilize the RSpec module and its DSL, include require "r_spec" in your spec files.
Many projects organize these includes through a custom spec helper.
Concrete test cases are defined in it blocks.
An optional (but recommended) descriptive string or module indicates the purpose of the test and a block contains the main logic of the test.
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.
An it block contains an example that should invoke the code to be tested and define what is expected of it.
Each example can contain multiple expectations, but it should test only one specific behaviour.
The its method can also be used to generate a nested example group with a single example that specifies the expected value (or the block expectations) of an attribute of the subject using is_expected.
To express an expectation, wrap an object or block in expect, call to (or not_to) and pass it a matcher object.
If the expectation is met, code execution continues.
Otherwise the example has failed and other code will not be executed.
In test files, specs can be structured by example groups which are defined by describe and context sections.
Typically a top level describe defines the outer unit (such as a class) to be tested by the spec.
Further describe sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
For unit tests, it is recommended to follow the conventions for method names:
- outer 
describeis the name of the class, innerdescribetargets methods; - instance methods are prefixed with 
#, class methods with.. 
To establish certain contexts — think empty array versus array with elements — the context method may be used to communicate this to the reader.
Unlike a describe block, all specifications executed within a context are isolated in a subprocess.
This prevents possible side effects on the Ruby object environment from being propagated outside their context, which could alter the result of the unit test suite.
Note: if you are wondering what kind of code might be generated by the DSL, an article that shows the dynamic transcription of the main methods with simple examples is available in Chinese, in English and in Japanese.
Expectations
Expectations define if the value being tested (actual) matches a certain value or specific criteria.
Equivalence
expect(actual).to eql(expected) # passes if expected.eql?(actual)
expect(actual).to eq(expected)  # passes if expected.eql?(actual)
Identity
expect(actual).to equal(expected) # passes if expected.equal?(actual)
expect(actual).to be(expected)    # passes if expected.equal?(actual)
Comparisons
expect(actual).to be_within(delta).of(expected) # passes if (expected - actual).abs <= delta
Regular expressions
expect(actual).to match(expected) # passes if expected.match?(actual)
Expecting errors
expect { actual }.to raise_exception(expected) # passes if expected exception is raised
True
expect(actual).to be_true # passes if true.equal?(actual)
False
expect(actual).to be_false # passes if false.equal?(actual)
Nil
expect(actual).to be_nil # passes if nil.equal?(actual)
Type/class
expect(actual).to be_instance_of(expected)    # passes if expected.equal?(actual.class)
expect(actual).to be_an_instance_of(expected) # passes if expected.equal?(actual.class)
Predicate
expect(actual).to be_xxx            # passes if actual.xxx?
expect(actual).to be_have_xxx(:yyy) # passes if actual.has_xxx?(:yyy)
Examples
expect([]).to be_empty
expect(foo: 1).to have_key(:foo)
Change
expect { object.action }.to change(object, :value).to(new)
expect { object.action }.to change(object, :value).from(old).to(new)
expect { object.action }.to change(object, :value).by(delta)
expect { object.action }.to change(object, :value).by_at_least(minimum_delta)
expect { object.action }.to change(object, :value).by_at_most(maximum_delta)
Satisfy
expect(actual).to(satisfy { |value| value == expected })
Running specs
By convention, specs live in the spec/ directory of a project. Spec files should end with _spec.rb to be recognizable as such.
Depending of the project settings, you may run the specs of a project by running rake spec (see Rake integration example section below).
A single file can also be executed directly with the Ruby interpreter.
Examples
Run all specs in files matching spec/**/*_spec.rb:
bundle exec rake spec
Run a single file:
ruby spec/my/test/file_spec.rb
It is not recommended, but the RSpec’s rspec command line might also work:
rspec spec/my/test/file_spec.rb
rspec spec/my/test/file_spec.rb:42
rspec spec/my/test/
rspec
Spec helper
Many projects use a custom spec helper file, usually named spec/spec_helper.rb.
This file is used to require r_spec/clone and other includes, like the code from the project needed for every spec file.
Rake integration example
The following Rakefile settings should be enough:
require "bundler/gem_tasks"
require "rake/testtask"
Rake::TestTask.new do |t|
  t.pattern = "spec/**/*_spec.rb"
end
task spec: :test
task default: :test
And then execute:
bundle exec rake
Performance
The benchmarks compare the performance of r_spec-clone with the following frameworks (in alphabetical order):
Boot time
Benchmark against 100 executions of a file containing 1 expectation (lower is better).
Runtime
Benchmark against 1 execution of a file containing 100,000 expectations (lower is better).
Test suite
RSpec clone’s specifications are self-described here: spec/
Contact
- Home page: https://r-spec.dev/
 - Cheatsheet: https://r-spec.dev/cheatsheet.html
 - Blog post: https://cyrilllllll.medium.com/introducing-a-new-rspec-850d48c0f901
 - Source code: https://github.com/cyril/r_spec-clone.rb
 - API Doc: https://rubydoc.info/gems/r_spec-clone
 - Twitter: https://twitter.com/cyri_
 
Special thanks ❤️
I would like to thank the whole RSpec team for all their work. It’s a great framework and it’s a pleasure to work with every day.
Without RSpec, this clone would not have been possible.
Buy me a coffee ☕
If you like this project, please consider making a small donation.
Versioning
RSpec clone follows Semantic Versioning 2.0.
License
The gem is available as open source under the terms of the MIT License.
One more thing
Under the hood, RSpec clone is largely animated by a collection of testing libraries designed to make programmers happy.
It’s a living example of what we can do combining small libraries together that can boost the fun of programming.