DEV Community

Cyril Kato
Cyril Kato

Posted on • Edited on

What Ruby code to expect from a testing DSL?

Have you ever wondered what the Ruby code generated by your test DSL might look like?

As a developer, it might be interesting to know. Maybe it's so horrible that it's impossible to write directly. Or maybe it's even lighter than a minitest test set. Who knows?

To try to answer this question while avoiding the endless debate between RSpec and minitest, I will use the RSpec clone project for the purpose of this post.

What is RSpec clone?

It is a minimalist reimplementation of RSpec that emphasizes correctness, security and standardization.

Driven by the desire to enforce the guidelines and best practices outlined in the community RSpec style guide, this RSpec clone includes most of RSpec’s DSL to express expected outcomes with no magical powers.

What did you RSpec?

Without further ado, here are some correspondences between the DSL syntax and the generated Ruby code to understand how the framework builds and executes tests. For ease of reading, these mappings are grouped by method.

From DSL to Ruby

describe method

When I write this,

RSpec.describe String do
end
Enter fullscreen mode Exit fullscreen mode

I expect this code to be generated:

Class.new do
  private

  def described_class
    String
  end
end
Enter fullscreen mode Exit fullscreen mode

context method

When I write this,

RSpec.context "when in a context" do
end
Enter fullscreen mode Exit fullscreen mode

I expect this code to be generated:

Class.new do
end
Enter fullscreen mode Exit fullscreen mode

subject method

When I write this,

RSpec.describe ".subject" do
  subject do
    "foo"
  end
end
Enter fullscreen mode Exit fullscreen mode

I expect this code to be generated:

Class.new do
  private

  def subject
    "foo"
  end
end
Enter fullscreen mode Exit fullscreen mode

Embedded describe method

When I write this,

RSpec.describe ".describe" do
  describe "embedded describe" do
  end
end
Enter fullscreen mode Exit fullscreen mode

I expect this code to be generated:

base = Class.new do
end

Class.new(base) do
end
Enter fullscreen mode Exit fullscreen mode

let method

When I write this,

RSpec.describe ".let" do
  let(:answer_to_everything) { 41 + one }
  let(:one) { 1 }
end
Enter fullscreen mode Exit fullscreen mode

I expect this code to be generated:

Class.new do
  private

  def answer_to_everything
    41 + one
  end

  def one
    1
  end
end
Enter fullscreen mode Exit fullscreen mode

before method

When I write this,

RSpec.describe ".before" do
  before do
    puts "hello"
  end
end
Enter fullscreen mode Exit fullscreen mode

I expect this code to be generated:

Class.new do
  private

  def initialize
    puts "hello"
  end
end
Enter fullscreen mode Exit fullscreen mode

expect method

When I write this,

RSpec.describe "#expect" do
  it { expect(41.next).to be(42) }
end
Enter fullscreen mode Exit fullscreen mode

I expect this code to be generated:

description = Class.new do
end

require "matchi/rspec"
require "r_spec/clone/expectation_target"

example = Class.new(description) { include Matchi::Helper }
sandbox = example.new
sandbox.instance_eval { ExpectationTarget::Value.new(41.next).to be(42) }
Enter fullscreen mode Exit fullscreen mode

And I expect to see this result in console:

Success: expected to be 42.
Enter fullscreen mode Exit fullscreen mode

Happy testing with Ruby

Unless you don't write tests, the advantage of using such DSL interface is to limit the introduction of additional logic in to the specification document with potential errors, to encourage the use of good Ruby patterns with low algorithmic complexity, and to make Ruby code shorter and more (machine) readable.

According to Adam Hawkins,

Rspec is a testing DSL. MiniTest is ruby.

I love both, and I hope you'll like RSpec clone too.

Top comments (0)