Testing Rack Applications using Rack::Test

July 19, 2022   • no-rails-app

This is the fourth post in the series on building a web application without using Rails. In the last post, we saw how to dynamically render the views templates. This post is all about testing.

As we add more features to our Rack-based web application, it’s getting difficult and tedious to manually test our application in the browser. It’s important to make sure that as we add new features or refactor the code, we don’t break the application. In this article, we will set up tests using the Rack::Test library along with RSpec.

Rack::Test

Rack::Test is a small, simple testing API for Rack apps. It can be used on its own or as a reusable starting point for Web frameworks and testing libraries to build on.

It provides the following features:

  • Allows for submitting requests and testing responses
  • Maintains a cookie jar across requests
  • Supports request headers used for subsequent requests
  • Follow redirects when requested

RSpec

RSpec is a ruby testing library. You can also use Minitest. Let’s use bundler to install rack-test and rspec gems.

bundle add rack-test
bundle add rspec

Now create a spec directory and add a spec_helper.rb in it.

mkdir spec
touch spec/spec_helper.rb

We will require the rack/test library along with our application in the spec_helper, so we don’t need to worry about them when writing specs.

require 'rack/test'
require_relative '../app'

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

Let’s add our first test that tests the home page. Create a spec/app_spec.rb file with the following test.

require 'spec_helper'

RSpec.describe App do
  let(:app) { App.new }
  let(:response_body) do
    <<~BODY
      <html>
        <head>
          <title>Application</title>
          <link rel="stylesheet" href="/public/style.css">
          <meta charset="utf-8">
        </head>

        <body>
          <main>
            <h1>Greetings from Rack</h1>
          </main>
        </body>
      </html>
    BODY
  end

  it 'can access the home page' do
    get '/'

    expect(last_response.status).to eq 200
    expect(last_response.body).to eq(response_body)
  end
end

To run the spec, use the rspec command. The spec should pass. If you run into any problems, let me know.

➜  web git:(main) ✗ rspec

Finished in 0.01113 seconds (files took 0.16989 seconds to load)
1 example, 0 failures

Congratulations, we have our first passing test. Let’s add another spec for the favicon.

it 'can read the favicon' do
  get '/favicon.ico'
  
  expect(last_response.status).to eq(200)
  expect(last_response.body).to eq('favicon')
end

The specs should still pass. This is pretty nice as we now have specs covering our entire application. We will build the future features in this series by writing a failing test and then making the test pass.


RSpec supports a .rspec configuration file, that lets you skip the spec_helper require in your spec files and also pretty-print the output in a nice document format that reads like English.

First, add a .rspec file in the root directory

touch .rspec

Add following content in it:

--color
--format documentation
--require spec_helper

If you run the rspec command now, you will get the following output:

➜  web git:(main) ✗ rspec

App
  can access the home page
  can read the favicon

Finished in 0.00969 seconds (files took 0.16714 seconds to load)
2 examples, 0 failures

Which is really nice, especially when our test suite gets larger and larger.

That’s it. I hope you learned something new. In the next article, we will learn how to add routing and controller support to our Rack application. This will allow use to use the separation-of-concerns principle to organize our application.