Sinatra app with RSpec

There are times when the only thing I want to create is a simple API. In the Ruby ecosystem the standard for creating something simple is Sinatra. But, there are a lot of things you miss in Sinatra that you have predefined in Rails. Sinatra let’s you define and include only the things you actually need. Maybe this is a good thing, maybe it is not.

However, the things you need for a complete Sinatra application are sometimes hard to include. This tutorial aims to help you in that process, by showing you how to create a simple Sinatra web application that uses Rspec for testing.


We will create a simple TODO application with only two actions. One for listing all the existing Todos and another one for adding a new one. It will use Sinatra for creating the API, Rpsec for testing, and it will also include Active Support for adding a couple of nice functionality to the Ruby language.

Bootstrap #

So let’s begin! The first thing is to create a Gemfile and list all the dependencies. From the above description of the project we can simply deduct all the things that are necessary.

source ""

gem "rack"
gem "sinatra"
gem "activesupport"

group :test do
  gem "rspec"
  gem "rack-test"

We can install the dependencies with bundle install. I like to put my dependencies in the vendor/bundle directory. With that the command becomes the following

bundle install --path vendor/bundle

Then we can init RSpec in for project. The following command should create an .rspec and a spec/spec_helper.rb file.

bundle exec rspec --init

As an old Rails habit, I like to have two folders in my project.

Let’s create them:

mkdir app
mkdir config

Configuration #

In this example project I will have only one configuration file called config/environment.rb. This file will be responsible for booting up the application and loading all the gems from Bundler. Also it will load and set up active support for this project.

Instead of using a database, this simple API will use only a global variable named $db. This is for the sake of simplicity and should be replaced with a real database.

With the above description we can implement that file like this

require "rubygems"
require "bundler"

Bundler.require(:default)                   # load all the default gems
Bundler.require(Sinatra::Base.environment)  # load all the environment specific gems

require "active_support/deprecation"
require "active_support/all"

$db = []                                    # a fake database

Now we should make the spec_helper load this file before the tests are run. To do that we need to prepend that file with a require statement.

While we are here it would be also a good idea to set the environment to test when running rspec, include some rake test helpers, and clear the fake database before each test.

Your spec/spec_helper.rb file should look similiar to this.

ENV['RACK_ENV'] = 'test'

require "./config/environment"

RSpec.configure do |config|
  config.include Rack::Test::Methods

  config.before(:each) do
    $db = []

  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true

  config.filter_run :focus
  config.run_all_when_everything_filtered = true


  config.warnings = true

    config.default_formatter = 'doc'

  config.profile_examples = 10

  config.order = :random

  Kernel.srand config.seed

The API #

As a good TDD abiding citizen we should write some test for our Todo application before actually implementing the code.

Create an app/todo_api.rb and spec/app/todo_api_spec.rb file

touch app/todo_api.rb
touch spec/app/todo_api_spec.rb

and describe the application in your test file

require "spec_helper"

RSpec.describe TodoApi do
  def app
    TodoApi # this defines the active application for this test

and then a matching class in the app file

class TodoApi < Sinatra::Base


Then we should create a get action for listing all the todos in the system. Such a get request should return nothing if the database is empty and a list of todos if the contains several todos

  describe "GET todos" do      
    context "no todos" do      
      it "returns no todo" do
        get "/"

        expect(last_response.body).to eq("")
        expect(last_response.status).to eq 200

    context "several todos" do
      before do
        @todos = ["hello", "world", "!"]
        $db = @todos

      it "returns all the todos" do   
        get "/"

        expect(last_response.body).to eq @todos.join("\n")
        expect(last_response.status).to eq 200

A matching implementation would be

  get "/" do

Creating a new todo is also simple. The todo message should be passed as an argument to the POST action.

  describe "POST todo" do
    it "returns status 200" do
      post "/", :todo => "hello rspec"

      expect(last_response.status).to eq 200

    context "todo param missing" do 
      it "returns status 400" do      
        post "/"

        expect(last_response.status).to eq 400

It’s implementation

  post "/" do
    return 400 unless params["todo"].present?

    $db << params["todo"]

Making rack happy and running the App #

In order to run your application with the rackup command
we should create an file with the following content

require "./config/environment"

run"/" => TodoApi)

and run our application with

bundle exec rackup

Hooray! Our simple API is finished.

The full source code can be found in this Github repo.


Now read this

Thor - Awesome CLI apps

Ever wanted to create an awesome command line application but was lost in the sea of options parsing? Well, if you did, and even if you didn’t, Thor is here to help you. But what is Thor exactly? It is a powerful toolkit for writing... Continue →