Testing a Sinatra App with Capybara
Gems
First things first. Add the following to your gemfile, if they’re not already there:
gem 'minitest'
gem 'capybara'
gem 'launchy'
gem 'database_cleaner'
Minitest: Capybara uses minitest. You should have model tests that use it too (unless you’re using something like Rspec instead).
Capybara: obviously you need capybara to run capybara.
Launchy: this allows you to run the save_and_open_page_path command when writing your tests. When you do this, it saves a copy of the html page your test currently has you on, and opens it in a browser — used for debugging.
Database cleaner: allows you to wipe and reset your database at the beginning and end of each test. More on this later.
Test database
In order to ensure that you don’t corrupt your actual data, make sure your tests are running off of a different database than development and production. Like this:
if ENV["RACK_ENV"] == "test"
database = Sequel.sqlite("db/skill_inventory_test.sqlite3")
else
database = Sequel.sqlite("db/skill_inventory_development.sqlite3") end #The following line is then in the test_helper.rb file so that branch is followed:
ENV["RACK_ENV"] = test
Test Helper
When writing tests, you’ll need to know exactly what is in the database, as that will inform your assertions. The only way to be sure of this is to start with a clean slate for each test. We used Database cleaner to do this. You already have the gem in your Gemfile, so setup is once again in our test_helper file. The following is in that file:
ENV["RACK_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__) require 'minitest/autorun'
require 'minitest/pride'
require 'capybara/dsl'
require 'tilt/erb' DatabaseCleaner[:sequel, {:connection => Sequel.sqlite("db/skill_inventory_test.sqlite3")}].strategy = :truncation Capybara.app = SkillInventoryApp Capybara.save_and_open_page_path = '../../tmp/capybara' module TestHelpers
def setup
DatabaseCleaner.start
super
end def teardown
DatabaseCleaner.clean
super
end def skill_inventory
database = Sequel.sqlite("db/skill_inventory_test.sqlite3")
@skill_inventory ||= SkillInventory.new(database)
end
end
The TestHelper module is included in each testing class. Minitest automatically runs both setup and teardown methods with each test, so you won’t need to call them manually.
Line 11 tells Capybara which controller to use when running the tests
Line 12 just tells the program where to save the html files that are generated when you run the save_and_open_page_path command, so you can add that file to your .gitignore file.
User Stories
Capybara tests are based on the user experience, which shouldn’t require any knowledge of what the code base looks like. First write a user story ( in the Agile sense of the word), and then write the test to represent it. The basic user story format is as follows:
- As a [user] (What kind of user? Guest? Authenticated? New? Existing but unauthenticated?)
- When I [do something]
- And I [do something else] …(however many times you need to ‘and I…’)
- Then I [expect this to happen]…(however many things you expect to happen)
Here’s a very basic example user story:
- As a user (the app here is very basic and has no authentication, so all users are the same kind)
- When I visit the home page
- And I click on the ‘new skill’ link
- And I fill in the ‘title’ field with a skill title
- And I fill in the ‘description’ field with a skill description
- And I select ‘school’ from the category drop-down menu
- And I click submit
- Then I should be redirected to the ‘skills’ page
- Then I should see my skill listed there with the above title and category
Capybara syntax
All that’s left to do after that is to translate the above user story into Capybara. My whole test looks like this (each line of the test can effectively be mapped to a line of the user story, above):
require_relative '../test_helper' class UserCreatesSkillTest < Minitest::Test
include Capybara::DSL
include TestHelpers def test_creates_a_skill_with_valid_attributes
visit '/'
click_link('New Skill')
fill_in("skill[name]", with: "Skill name")
fill_in("skill[description]", with: "Skill description")
select "School", from: "category_id"
click_button("Submit") assert_equal '/skills', current_path within("#skill") do
assert page.has_content?("Skill name")
assert page.has_content?("school")
end
refute page.has_content?("work")
end
end
visit, fill_in, select, click_button, current_path, within, and has_content? are all built in Capybara methods. A more complete list of available methods and how to use them can be found here
All that’s left from there is to run the test and see the rainbow!
You should, of course, have tests for each piece of your CRUD functionality, both for when the user does everything as expected, as well as edge cases and exceptions. The process will be the same for all of these. Happy testing!