Ruby Inline Tests

Did you know you can inline tests with your ruby code in the same file?

Posted by Anton Katunin on 31 May 2016
Tags: code

After reading the title Embedding a Test Suite in a Single-file Ruby App (Part 1) I thought: aha! finally somebody has done it and used ruby data block to store tests. However I was disappointed as there was another approach, which I think is less elegant.

Below I show you a proof of concept of embedding tests in ruby code.

Why?

Because tests and code are in the same file:

  • easier to refactor
  • easier to navigate
  • documentation from tests near the code
  • does not affect code performance
  • reminds you to write tests

How

Ruby has special feature which I call data blocks. In short you can embed any data after your ruby code in the same file and when you run that file that data will be available as DATA constant. All you need is to separate it with __END__.

For example

$ cat t.rb
puts DATA.gets
__END__
hello world!

$ ruby t.rb
hello world!

Putting all together

Below is my proof of concept to run rspec tests from the same file.

# drink.rb

class Drink
  attr_reader :type

  def initialize
    @type = "water"
  end
end

__END__
require 'spec_helper'

RSpec.describe Drink do
   it 'is has default type water' do
    expect(Drink.new.type).to eq('water')
  end
end

In order for that to work we need to put small patch to rspec so it reads the file correctly.

# spec_helper.rb
class DataBlockLoader

  module RspecConfig
    def load_spec_files
      super
      files_to_run.uniq.each do |f|
        file = File.expand_path(f)

        DataBlockLoader.load file
      end
    end
  end

  END_TOKEN = /^__END__\r?\n/

  def self.load file_path
    io = File.open(file_path, 'r' )

    loop do
      line = io.gets

      break if line.nil?
      break if line.match(END_TOKEN)
    end

    line_no = io.lineno

    eval io.read, nil, file_path, line_no + 1
  end
end

RSpec::Core::Configuration.prepend DataBlockLoader::RspecConfig

Finally

Now we can just run that file with rspec:

$ rspec drink.rb

 1/1 |========================= 100 ==========================>| Time: 00:00:01

Finished in 0.86 seconds (files took 0.70232 seconds to load)
1 example, 0 failures

This is just a small proof of concept which requires more work on adopting rspec internals to work with data blocks. Let's me know if you'll take a challenge on that.


Read next:

Rails as a static site generator