RSpec clone

A minimalist RSpec clone with all the essentials.

What did you RSpec?


Home Version Yard documentation CI RuboCop License

Project goals

  1. Keep a low level of code complexity, avoid false negatives and false positives.
  2. Translate specification documents into atomic and thread safe Ruby objects.
  3. Avoid overloading the interface with additional alternative syntaxes.
  4. Provide most of RSpec's DSL to express expected outcomes of a code example.

Some differences

  • There is no option to activate monkey-patching.
  • It does not rely on hacks such as at_exit hook to trigger the tests.
  • Malicious actual values cannot hack results.
  • If no subject has been explicitly determined, none is defined.
  • If no described class is set, described_class is undefined instead of nil.
  • Expectations cannot be added inside a before block.
  • Arbitrary helper methods are not exposed to examples.
  • The let method defines a helper method rather than a memoized helper method.
  • The one-liner is_expected syntax also works with block expectations.
  • subject, before, after and let definitions must come before examples.
  • Runs much faster.


Add this line to your application's Gemfile:

gem "r_spec-clone"

And then execute:


Or install it yourself as:

gem install r_spec-clone


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:

RSpec clone demo


Anatomy of a spec file

To use the RSpec module and its DSL, you need to add require "r_spec" to your spec files. Many projects use a custom spec helper which organizes these includes.

Concrete test cases are defined in it blocks. An optional descriptive string states it's purpose and a block contains the main logic performing 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 are 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 describe is the name of the class, inner describe targets 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.

To execute unit tests while isolating side effects in a sub-process, declined methods can be used: describe!, context!, it!, its!. Here is an example:

app = "foo"

RSpec.describe "Side effects per example" do
  it! "runs the example in isolation" do
    expect { app.gsub!("foo", "bar") }.to eq "bar"
    expect(app).to eq "bar"

  it "runs the example" do
    expect(app).to eq "foo"

# Success: expected to eq "bar".
# Success: expected to eq "bar".
# Success: expected to eq "foo".

Note: if you are wondering what the Ruby code generated by using the DSL might look like, an article presents the correspondence between each method via simple examples, available in English, Chinese and Japanese.


Expectations define if the value being tested (actual) matches a certain value or specific criteria.


expect(actual).to eql(expected) # passes if expected.eql?(actual)
expect(actual).to eq(expected)  # passes if expected.eql?(actual)


expect(actual).to equal(expected) # passes if expected.equal?(actual)
expect(actual).to be(expected)    # passes if expected.equal?(actual)


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


expect(actual).to be_true # passes if true.equal?(actual)


expect(actual).to be_false # passes if false.equal?(actual)


expect(actual).to be_nil # passes if nil.equal?(actual)


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)


expect(actual).to be_xxx            # passes if
expect(actual).to be_have_xxx(:yyy) # passes if actual.has_xxx?(:yyy)
expect([]).to be_empty
expect(foo: 1).to have_key(:foo)


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)


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.


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/

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" do |t|
  t.pattern = "spec/**/*_spec.rb"

task spec: :test
task default: :test

And then execute:

bundle exec rake


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).

Boot time benchmark


Benchmark against 1 execution of a file containing 100,000 expectations (lower is better).

Runtime benchmark

Test suite

RSpec clone's specifications are self-described here: spec/


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 to Batman.



RSpec clone follows Semantic Versioning 2.0.



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.

Fix testing tools logo for Ruby