This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.
Ruby is one of the most popular languages used on the web. We’re running a Session here on Nettuts+ that will introduce you to Ruby, as well as the great frameworks and tools that go along with Ruby development. In this episode, you'll learn about testing your Sinatra apps with Cucumber, Capybara, and Rspec.
In the previous tutorial in this series, we looked at Rspec, and how you can do test-driven development with it. I mentioned that, in the next episode, we’d look at using Rspec to test web apps. However, a couple of commenters mentioned they’d be interested in seeing testing with Cucumber, so that’s what we’ll use to test a very simple web app instead. Actually, we’ll be using part of Rspec to get the same matcher syntax as you saw last time, but the actual test setup is in Cucumber.
Prefer a Screencast?
Step 1: Building the App
We’re going to create an incredibly simple Sinatra app to test. For starters, let’s create a project folder and throw this in a Gemfile:
What? You don’t know
Gemfiles? Get out from under that rock and catch up with episode 8
source :rubygems gem "sinatra" gem "shotgun" gem "cucumber" gem "capybara" gem "rspec"
bundle install in that directory. With the gems installed, we’re ready to go! (Optionally—if you’re using RVM—you could install these gems for this project only by running
rvm --rvmrc --create 1.9.2@cucumber_example; run this before
So, open a file called
myapp.rb; here’s our super simple app; it just simulates a site that might let you sign up for a newsletter.
require "sinatra/base" class MyApp < Sinatra::Base get "/" do erb :index end post "/thankyou" do @name = params["name"] @email = params["email"] erb :thankyou end get "/form" do erb :form end end
If you’re not familiar with Sinatra, check out Dan Harper’s excellent sessions Singing with Sinatra; that’ll get you up and running with the basics in no time.
If you are familiar with Sinatra, you’ll see that we’re creating three paths here; on the home page (’/’), we just render the
index.erb template (more on the templates in a minute). If we get a post request to the path
/thankyou, we take the values of the
thankyou.erb. Finally, at
/form, we render the
Now, let’s build these templates. Sinatra will look inside a ‘views’ folder for the templates, so let’s put them there. As you saw in
myapp.rb, we’re using ERB to render the templates, so they’ll, of course, be ERB templates. If we have a
layout.erb template, it will wrap all our other templates. So, let’s do this:
<!DOCTYPE html> <html> <head> <meta charset='UTF=8' /> <title>THE APP</title> </head> <body> <h1>THE APP</h1> <%= yield %> </body> </html>
That call to
yield will be where the other templates are inserted. And those other templates are pretty simple:
<p>This is the home page</p> <p><a id="link" href="/form">Sign up for our newsletter!</a></p>
<form method="post" action="/thankyou"> <p> Fill out this form to receive our newsletter. </p> <p> <label for="name">Name:</label> <input type="text" name="name" id="name" /> </p> <p> <label for="email">Email:</label> <input type="text" name="email" id="email" /> </p> <p> <button type="submit">Sign Up!</button> </p> </form>
<p>Hi there, <%= @name %>. You'll now receive our email at <%= @email %></p>
So, there’s our app. To test it manually, you can put this in a
require "./myapp" run MyApp
And then run
shotgun in the terminal. This will start up a websever, probably on port 9393. You can now poke around and test our web app. But we want to automate this testing, right? Let’s do it!
Step 2: Setting our our Test Environment
Cucumber bills itself as “behaviour driven development with elegance and joy.” While joy seems a bit far-fetched to me, I think we’ll both agree that elegance, well, Cucumber’s got it.
Because behaviour driven development is partly about understanding what the client wants before you begin coding, Cucumber aims to make its tests readable by clients (AKA, non-programmers). So, you’ll see here that all your tests are written in what appears to be plain text (it’s actually Gherkin).
Remember how, with Rspec, we has separate spec files to describe different functionalities? In Cucumber-speak, those are features, and they all belong in a “features” folder. Inside that folder create two more folders called “support” and “step_definitions.”
Inside the “support” folder, open an
env.rb. This code will set up our testing environment. Here’s what we need:
require_relative "../../myapp" require "Capybara" require "Capybara/cucumber" require "rspec" World do Capybara.app = MyApp include Capybara::DSL include RSpec::Matchers end
This requires the different libraries that we need, and uses
include to load their methods into our environment. What’s this Capybara that we’re using? Basically, it’s the functionality that allows us to use our web app, so that we can test it. It’s important to set
Capybara.app to our app. I should mention that, were we doing this with a Rails app, most of this setup would be done automatically for us.
(Note: in the screencast, I
include RSpec::Expectations unneccessarily; leave it out.)
Okay, so, let’s write some tests!
Step 3 Writing the Tests
Let’s start with our home page. Open the file
home_pages.feature (in the “features” folder) and start with this:
Feature: Viewer visits the Home Page In order to read the page As a viewer I want to see the home page of my app
This is a common way to start a feature file starts; Doesn’t really look like code, does it? Well, it’s Gherkin, a domain-specific languages (DSL) that “lets you describe software’s behaviour without detailing how that behaviour is implemented.” What we’re written so far doesn’t run in any way, but it explains the purpose of the feature. Here’s the general structure:
In order to [goal] As a [role] I want [feature]
You don’t have to follow that template: you can put whatever you want; the purpose is to describe the feature. However, this seems to be a common pattern.
Next comes a list of scenarios that describe the feature. Here’s the first:
Scenario: View home page Given I am on the home page Then I should see "This is the home page."
Each scenario can have up to three parts:
- Given -
Givenlines describe what pre-condition should exist.
- When -
Whenlines describe the actions you take.
- Then -
Thenlines describe the result.
There are also
And lines, which do whatever the line above them does. For example:
Given I am on the home page And I am signed in Then I should see "Welcome Back!" And I should see "Settings"
In this case, the first
And line acts as a
Given line, and the second one acts as a
We’ll see a few
When lines shortly. But right now, let’s run that test. To do that, run
cucumber in the terminal. You’ll probably see something like this:
Cucumber feature files are written to be readable to non-programmers, so we have to “implement step definitions for undefined steps.” Thankfully, Cucumber gives us some snippets to start with.
Looking at these snippets, you can see how this will work. Each step is matched with a regular expression. Any quoted values will be captured and passed as a block parameter. Inside the block, we do whatever we expect to happen as a result of that step. This might be set-up code in
Given steps, some calculations or actions in
When steps, and a comparison in
Cucumber will load any files in the folder “features/step_definitions” for steps, so let’s create “sinatra_steps.rb” file and add these two steps:
Given /^I am on the home page$/ do visit "/" end Then /^I should see "([^"]*)"$/ do |text| page.should have_content text end
In this little snippet here, we’re using Cucumber, Rspec, and Capybara. Firstly, we’ve got the cucumber
Then method calls. Secondly, we’re using the Capybara methods
visit (to visit a URL) and
has_content?. But you don’t see the call to
has_content? because we’ve loaded the RSpec matchers, so we can make our tests read as they would with Rspec. If we wanted to leave RSpec out, we would just write
Now, if you run
cucumber again, you’ll see that our tests pass:
Let’s add two more Scenarios for our home page:
Scenario: Find heading on home page Given I am on the home page Then I should see "MY APP" in the selector "h1" Scenario: Find the link to the form Given I am on the home page Then I should see "Sign up for our newsletter." in a link
These require two more
Then steps, as you’ll find if you try to run this. Add these to
Then /^I should see "([^"]*)" in the selector "([^"]*)"$/ do |text, selector| page.should have_selector selector, content: text end Then /^I should see "([^"]*)" in a link$/ do |text| page.should have_link text end
You should be able to tell what these are doing: the first looks for text within a certain element on the page. The second looks for a link with the given text (yes, you could have done
Then I should see "Sign up ..." in the selector "a", but I wanted to should you another Capybara/Rspec method)
cucumber; you’ll see all our tests passing:
Let’s now open “features/form_page.feature”; throw this in there:
Feature: Viewer signs up for the newsletter In order to recieve the newsetter As a user of the website I want to be able to sign up for the newsletter Scenario: View form page Given I am on "/form" Then I should see "Fill out this form to receive our newsletter." Scenario: Fill out form Given I am on "/form" When I fill in "name" with "John Doe" And I fill in "email" with "email@example.com" And I click "Sign Up!" Then I should see "Hi there, John Doe. You'll new receive our email newsletter at firstname.lastname@example.org"
The first scenario here is pretty simple, although we need to write the
Given step for is. You can probably figure out how to do that by now:
Given /^I am on "([^"]*)"$/ do |path| visit path end
The second one is a little more in depth. For the first time, we’re using
When steps (remember, the
And steps that follow the
When step are also
When steps). It’s pretty obvious what those
When steps should do, but how do we do that in the Ruby code? Thankfully, Capybara has a few handy methods to help up:
When /^I fill in "([^"]*)" with "([^"]*)"$/ do |element, text| fill_in element, with: text end When /^I click "([^"]*)"$/ do |element| click_on element end
We’re using the
fill_in method, which takes the name or id attribute of an element on the page. We’re also using
click_on, which will click on the element with the given text, id, or value. There are also the more specific
click_button. To see more, check out the Capybara Readme. Browse around the “DSL” section to see more of the methods that Capybara offers.
When you run
cucumber now, you should get all our tests, passing:
Realize that what we’re testing here is the just UI, not the underlying code. If our code really signed you up for the newsletter, we’d also have to test the code that adds the name and email address to our database, etc. Just because we see what we’re supposed to see, doesn’t mean we’ll actually receive the newsletter, right? That should be tested separately.
And that’s testing web apps with Cucumber, Capybara, and Rspec matchers. As I mentioned, check out the Capybara docs for more!