Sinatra is a fantastic lightweight framework for building web services. We’ll use it as the application framework for the HTTP endpoints in our Service Oriented Architecture.
The testing approach will be in-process, which means that the test suite is running in the same Ruby process as the web service. This eliminates the need to run an external HTTP web server.
Unlike Rails, Sinatra isn’t all that opinionated on how you set up your application (it has a few sensible defaults), which can lead to a lot of open questions on how to structure the application.
Here’s the directory structure we’ll use for our example application.
app/
app/models
app/my_service.rb
client/
client/lib/my_client.rb
client/my_client.gemspec
config/
spec/
For internal services we’ll build a client gem directly into the project. With the client embedded in our codebase we can follow outside-in development cycles — Features start with request specs from the client side, followed by the addition of end points to the service, and then unit tests within the application.
This allows the us to dogfood our client gem and bring it in as the first step of our development process.
To achieve this we need to configure a few things.
1. Set up the project gemfile to use a local copy of the client in test mode
Include the local client gem in test suite.
# Gemfile
group :test do
gem 'my_client', path: 'client'
gem 'webmock'
# ...
end
2. Use webmock to send all http requests to the service
Instead of booting up a web server every time the test suite is run we’ll mount the Sinatra service as a rack application with webmock.
This allows the client to talk directly to the mounted rack application without going through HTTP or a web server.
# spec/spec_helper.rb
RSpec.configure do |config|
config.include WebMock::API
config.before(:each) do
MyClient.base_url = 'http://www.example.com'
stub_request(:any, /www.example.com/).to_rack(MyService)
end
end
3. Use the client in our request specs
Once MyService is mounted as a rack application we can use the client
gem directly in our test suite.
# spec/requests/widget_management_spec.rb
require 'spec_helper'
describe "Widget management" do
it "creates a Widget" do
# set up fixture data if needed
response = MyClient::Widget.create(widget_params)
# assert expectations on the response
end
end
To use the client gem in other projects we can use a private gem hosting service like Gemfury. This allows us to include the client via gemfile in our other projects.
# Gemfile
source 'https://452f6E403CDph10714e41@gem.fury.io/me/'
gem 'my_client'
source 'https://rubygems.org
# ...
Written by Harlow Ward
Chances are, some of you have run into the issue with the invalid byte sequence in UTF-8 error when dealing with user-submitted data. A Google search shows that my hunch isn’t off.
Among the search results are plenty of answers—some using the deprecated iconv library—that might lead you to a sufficient fix. However, among the slew of queries are few answers on how to reliably replicate and test the issue.
In developing the Griddler gem we ran into some cases where the data being posted back to our controller had invalid UTF-8 bytes. For Griddler, our failing case needs to simulate the body of an email having an invalid byte, and encoded as UTF-8.
What are valid and invalid bytes? This table on Wikipedia tells us bytes 192, 193, and 245-255 are off limits. In ruby’s string literal we can represent this by escaping one of those numbers:
> "hi \255"
=> "hi \xAD"
There’s our string with the invalid byte! How do we know for sure? In that IRB session we can simulate a comparable issue by sending a message to the string it won’t like - like split or gsub.
> "hi \255".split(' ')
ArgumentError: invalid byte sequence in UTF-8
from (irb):9:in `split'
from (irb):9
from /Users/joel/.rvm/rubies/ruby-1.9.3-p125/bin/irb:16:in `<main>'
Yup. It certainly does not like that.
Let’s create a very real-world, enterprise-level, business-critical test case:
invalid_byte_spec.rb
require 'rspec'
def replace_name(body, name)
body.gsub(/joel/, name)
end
describe 'replace_name' do
it 'removes my name' do
body = "hello joel"
replace_name(body, 'hank').should eq "hello hank"
end
it 'clears out invalid UTF-8 bytes' do
body = "hello joel\255"
replace_name(body, 'hank').should eq "hello hank"
end
end
The first test passes as expected, and the second will fail as expected but not with the error we want. By adding that extra byte we should see an exception raised similar to what we simulated in IRB. Instead it’s failing in the comparison with the expected value.
1) replace_name clears out invalid UTF-8 bytes
Failure/Error: replace_name(body, 'hank').should eq "hello hank"
expected: "hello hank"
got: "hello hank\xAD"
(compared using ==)
# ./invalid_byte_spec.rb:17:in `block (2 levels) in <top (required)>'
Why isn’t it failing properly? If we pry into our running test we find out that inside our file the strings being passed around are encoded as ASCII-8BIT instead of UTF-8.
[2] pry(#<RSpec::Core::ExampleGroup::Nested_1>)> body.encoding
=> #<Encoding:ASCII-8BIT>
As a result we’ll have to force that string’s encoding to UTF-8:
it 'clears out invalid UTF-8 bytes' do
body = "hello joel\255".force_encoding('UTF-8')
replace_name(body, 'hank').should_not raise_error(ArgumentError)
replace_name(body, 'hank').should eq "hello hank"
end
By running the test now we will see our desired exception
1) replace_name clears out invalid UTF-8 bytes
Failure/Error: body.gsub(/joel/, name)
ArgumentError:
invalid byte sequence in UTF-8
# ./invalid_byte_spec.rb:4:in `gsub'
# ./invalid_byte_spec.rb:4:in `replace_name'
# ./invalid_byte_spec.rb:17:in `block (2 levels) in <top (required)>'
Finished in 0.00426 seconds
2 examples, 1 failure
Now that we’re comfortably in the red part of red/green/refactor we can move on to getting this passing by updating our replace_name method.
def replace_name(body, name)
body
.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
.gsub(/joel/, name)
end
And the test?
Finished in 0.04252 seconds
2 examples, 0 failures
For such a small piece of code we admittedly had to jump through some hoops. Through that process, however, we learned a bit about character encoding and how to put ourselves in the right position—through the red/green/refactor cycle—to fix bugs we will undoubtedly run into while writing software.
Zeus improves Rails boot time. Saving seconds is most important when running focused tests:
rspec spec/models/user_spec.rb
rspec spec/models/user_spec.rb:123
Those are times when a tight feedback loop make a meaningful difference.
Install the Zeus gem on your machine:
gem install zeus
Do not include it in your Gemfile. It is an external piece of software.
Initialize:
zeus init
This will create two files in your Rails app’s directory. Ignore them globally in ~/.gitignore:
custom_plan.rb
zeus.json
Edit zeus.json to include only the tasks for which you’ll use Zeus. Mine looks like this:
{
"command": "ruby -rubygems -r./custom_plan -eZeus.go",
"plan": {
"boot": {
"default_bundle": {
"development_environment": {
"prerake": {"rake": []},
"console": ["c"],
"generate": ["g"]
},
"test_environment": {
"test_helper": {"test": ["rspec"]}
}
}
}
}
}
I remove cucumber in favor of RSpec and Capybara. I remove server in favor of Foreman and Pow.
In spec/spec_helper.rb, change:
ENV['RAILS_ENV'] ||= 'test'
To:
ENV['RAILS_ENV'] = 'test'
The goal is to run tests in the context of Zeus. So, remove other similar systems.
From the RSpec docs:
> Generally, life is simpler if you just use the rspec command. If you
> must use the ruby command, however, you’ll want to do the following:
require 'rspec/autorun'
> This tells RSpec to run your examples.
We don’t need this behavior and can cause bugs when used with Zeus.
Remove either of these lines in spec/spec_helper.rb if they exist:
require 'rspec/autorun'
require 'rspec/autotest'
For the same reasons, if you’re using Spork and Guard, delete them from your Gemfile, delete your Guardfile, and delete any related Spork code in spec/spec_helper.rb or spec/support/.
Zeus will need to be running before you can use its commands:
zeus start
I usually run this, and other long-running processes in a tmux session.
Now, those original commands will have the benefit of Rails boot time in under a second:
zeus rspec spec/models/user_spec.rb
zeus rspec spec/models/user_spec.rb:123
Many of us are running specs directly from vim. If you edit your ~/.vimrc to use Zeus like in this commit, you can run focused specs with:
t
Enjoy!
Written by Dan Croak.
During my Test-Driven Rails workshop earlier this week (which is also available as an online workshop), my students and I were writing acceptance tests surrounding marking todo items as complete. The spec looked like this:
feature 'Manage todos' do
scenario 'view only todos the user has created' do
sign_in_as 'other@example.com'
create_todo_titled 'Lay eggs'
sign_in_as 'me@example.com'
user_should_not_see_todo_titled 'Lay eggs'
end
scenario 'complete my todos' do
sign_in_as 'person@example.com'
create_todo_titled 'Buy eggs'
complete_todo_titled 'Buy eggs'
user_should_see_completed_todo_titled 'Buy eggs'
end
scenario 'mark my todos incomplete' do
sign_in_as 'person@example.com'
create_todo_titled 'Buy eggs'
complete_todo_titled 'Buy eggs'
mark_incomplete_todo_titled 'Buy eggs'
user_should_see_incomplete_todo_titled 'Buy eggs'
end
def create_todo_titled(title)
click_link 'Create a new todo'
fill_in 'Title', with: title
click_button 'Create'
end
def user_should_see_todo_titled(title)
within 'ol.todos' do
expect(page).to have_css 'li', text: title
end
end
def user_should_see_completed_todo_titled(title)
within 'ol.todos' do
expect(page).to have_css 'li.complete', text: title
end
end
def user_should_see_incomplete_todo_titled(title)
within 'ol.todos' do
expect(page).not_to have_css 'li.complete', text: title
end
end
def user_should_not_see_todo_titled(title)
within 'ol.todos' do
expect(page).not_to have_css 'li', text: title
end
end
def complete_todo_titled(title)
todo = Todo.where(title: title).first
within("[data-id='#{todo.id}']") { click_link 'Complete' }
end
def mark_incomplete_todo_titled(title)
todo = Todo.where(title: title).first
within("[data-id='#{todo.id}']") { click_link 'Incomplete' }
end
end
There’s a handful of things that can probably be refactored in the helper methods, but that’s not what I wanted to focus on; check out the scenarios themselves:
scenario 'create a new todo' do
sign_in_as 'person@example.com'
create_todo_titled 'Buy eggs'
user_should_see_todo_titled 'Buy eggs'
end
scenario 'view only todos the user has created' do
sign_in_as 'other@example.com'
create_todo_titled 'Lay eggs'
sign_in_as 'me@example.com'
user_should_not_see_todo_titled 'Lay eggs'
end
scenario 'complete my todos' do
sign_in_as 'person@example.com'
create_todo_titled 'Buy eggs'
complete_todo_titled 'Buy eggs'
user_should_see_completed_todo_titled 'Buy eggs'
end
scenario 'mark completed todo as incomplete' do
sign_in_as 'person@example.com'
create_todo_titled 'Buy eggs'
complete_todo_titled 'Buy eggs'
mark_incomplete_todo_titled 'Buy eggs'
user_should_see_incomplete_todo_titled 'Buy eggs'
end
While each of these lines reads well, the subject of each line is the user, or “you”. You sign in, you create a todo titled “Buy eggs”, and you should see a todo with the correct title; the focus should be the todo. The todo is the subject of the test; we’re making assertions about if it’s on the page and its state after certain page interactions occur. The other thing we’re doing is repeating the todo title everywhere, which seems too verbose.
What if we used a Page Object?
scenario 'create a new todo' do
sign_in_as 'person@example.com'
todo = todo_on_page
todo.create
expect(todo).to be_visible
end
scenario 'view only todos the user has created' do
sign_in_as 'other@example.com'
todo = todo_on_page
todo.create
sign_in_as 'me@example.com'
expect(todo).not_to be_visible
end
scenario 'complete my todos' do
sign_in_as 'person@example.com'
todo = todo_on_page
todo.create
todo.mark_complete
expect(todo).to be_complete
end
scenario 'mark completed todo as incomplete' do
sign_in_as 'person@example.com'
todo = todo_on_page
todo.create
todo.mark_complete
todo.mark_incomplete
expect(todo).not_to be_complete
end
Aside from signing in, everything focuses on the todo since it’s the star of
the show. All that needs to be done is move the helper methods from the
original code into methods #create, #mark_complete, #mark_incomplete,
#visible?, and #complete?. First, we’ll start with #todo_on_page, though:
def todo_on_page
TodoOnPage.new('Buy eggs')
end
An instance of TodoOnPage, instantiated with a specific title, is returned
which will implement the handful of methods enumerated above.
class TodoOnPage < Struct.new(:title)
include Capybara::DSL
def create
click_link 'Create a new todo'
fill_in 'Title', with: title
click_button 'Create'
end
def mark_complete
todo_element.click_link 'Complete'
end
def mark_incomplete
todo_element.click_link 'Incomplete'
end
def visible?
todo_list.has_css? 'li', text: title
end
def complete?
todo_list.has_css? 'li.complete', text: title
end
private
def todo_element
find 'li', text: title
end
def todo_list
find 'ol.todos'
end
end
By using well-named methods like #todo_element and #todo_list, it becomes
immediately obvious how to mark todos complete and incomplete, as well as how
to check if a todo is complete or on the page.
The TodoOnPage is a page object. Given a
specific context (in this case, the title of a todo), it encapsulates page
interaction (#create, #mark_complete, #mark_incomplete) and assertions
(using RSpec’s predicate matchers with #visible? and #complete?).
While I’ve written plenty of page objects before, often they only involve interaction and not predicate methods for matchers. Including both seems totally obvious now; I’m really excited to start using this pattern throughout other areas of my acceptance testing.
Detect emerging problems in your codebase with Ruby Science. We’ll deliver solutions for fixing them, and demonstrate techniques for building a Ruby on Rails application that will be fun to work on for years to come.

Stability can become an issue as web applications evolve and grow — integration tests provide a great way to perform end-to-end tests that validate the application is performing as expected.
When writing integration tests, try to model the test around an actor (user of the system) and the action they are performing.
# spec/features/visitor_signs_up_spec.rb
require 'spec_helper'
feature 'Visitor signs up' do
scenario 'with valid email and password' do
sign_up_with 'valid@example.com', 'password'
expect(page).to have_content('Sign out')
end
scenario 'with invalid email' do
sign_up_with 'invalid_email', 'password'
expect(page).to have_content('Sign in')
end
scenario 'with blank password' do
sign_up_with 'valid@example.com', ''
expect(page).to have_content('Sign in')
end
def sign_up_with(email, password)
visit sign_up_path
fill_in 'Email', with: email
fill_in 'Password', with: password
click_button 'Sign up'
end
end
To share code between features move common capybara steps into a Ruby module in the rspec support directory.
# spec/support/features/session_helpers.rb
module Features
module SessionHelpers
def sign_up_with(email, password)
visit sign_up_path
fill_in 'Email', with: email
fill_in 'Password', with: password
click_button 'Sign up'
end
def sign_in
user = create(:user)
visit sign_in_path
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
click_button 'Sign in'
end
end
end
Modules must be explicitly included to share the common code between integration tests.
# spec/support/features.rb
RSpec.configure do |config|
config.include Features::SessionHelpers, type: :feature
end
$ rspec -fd
Visitor signs up
with valid email and password
with invalid email
with blank password
Finished in 0.35837 seconds
3 examples, 0 failures
By Harlow Ward
Describe the User’s Perspective: DDD, acceptance testing, and you
The Quest Continues: Introducing capybara-webkit
Detect emerging problems in your codebase with Ruby Science. We’ll deliver solutions for fixing them, and demonstrate techniques for building a Ruby on Rails application that will be fun to work on for years to come.