Testing Ruby’s CGI
2018-10-12
CGI is a standard for generating HTML pages from scripts executed as child processes by a web server. I explained CGI and how I use it in a previous post, CGI: Ruby’s Bare Metal. I like to use CGI because it means that I don’t have to run any Ruby app server (e.g. puma, passenger, unicorn) 24/7. Less moving parts == more robust!
Those app servers get most of the press these days so sadly CGI doesn’t have much support and documentation; I couldn’t find anything on how to test CGI scripts. These scripts handle my business, they need to work so I decided to put some effort into testing CGI. You, dear reader, are the benefactor!
Two things I learned:
- Webrick, Ruby’s built-in HTTP server, does have support for calling CGI scripts. This will be critical for us, it means we don’t have to install Apache or nginx to call the CGI scripts.
- Because CGI outputs raw HTML, we’ll need to do browser-level testing using Capybara with headless chrome or another automated browser.
Great! Our test suite will be a Ruby process with two things:
- Webrick serving CGI requests
- Capybara requesting URLs from Webrick
How can a Ruby process do two things at once? With Threads, of course!
Webrick
Webrick is set up out of the box to serve any .cgi
files as Ruby CGI scripts.
You point Webrick to your DocumentRoot
as normal. Note that since
Capybara is going to be driving the tests, we need Webrick to execute in
another thread so both the HTTP client and server can execute
concurrently:
WEBRICK = Thread.new do
require 'webrick'
server = WEBrick::HTTPServer.new(
:Port => 8999,
:DocumentRoot => File.expand_path("../..", __FILE__),
)
trap('INT') { server.shutdown }
puts "Starting Webrick on port 8999"
server.start
end
Capybara
Most of the Capybara integration is straight out of the README, there is only one trick necessary: since we are starting our own custom Webrick server in a separate thread, we need to tell Capybara not to start a server on its own and point it to ours instead.
Capybara.run_server = false
Capybara.app_host = 'http://localhost:8999'
After an hour of fiddling, here’s the test/helper.rb
I came up with:
Thread.report_on_exception = true
require 'minitest/autorun'
require 'capybara/minitest'
Capybara.run_server = false
# I found poltergeist quick to install and worked first time, YMMV
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
class CGITest < Minitest::Test
include Capybara::DSL
include Capybara::Minitest::Assertions
def setup
Capybara.current_driver = Capybara.javascript_driver
end
def teardown
Capybara.reset_sessions!
Capybara.use_default_driver
end
end
# Here's the trick:
# We need to run the Webrick server in a separate thread so the
# testcases can make requests and block, waiting for the response.
WEBRICK = Thread.new do
require 'webrick'
server = WEBrick::HTTPServer.new(
:Port => 8999,
:DocumentRoot => File.expand_path("../..", __FILE__),
)
trap('INT') { server.shutdown }
puts "Starting Webrick on port 8999"
server.start
end
# Point Capybara to Webrick!
Capybara.app_host = 'http://localhost:8999'
# give webrick time to boot before the tests can run
sleep 1
Now I create test/smoke_test.rb
:
require_relative './helper'
class SmokeTest < CGITest
def test_pro_navigation
visit("/spro/new.cgi")
assert page.has_content?('Subscribe to')
visit("/spro/update.cgi")
assert page.has_content?('Update your')
visit("/spro/cancel.cgi")
assert page.has_content?('Cancel your')
visit("/spro/delete.cgi")
assert page.has_content?('Confirm your')
end
end
That’s a quick test to make sure the billing scripts for Sidekiq Pro will render correctly and not crash. Now with a little test coverage I’ve got more confidence that changes won’t break these scripts.