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
Gemfile
s? Get out from under that rock and catch up with episode 8
source :rubygems gem "sinatra" gem "shotgun" gem "cucumber" gem "capybara" gem "rspec"
Now, run 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 bundle install
)
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 name
and email
parameters and assign them to instance variables. Instance variables will be available inside whatever template we render, which happens to be thankyou.erb
. Finally, at /form
, we render the form.erb
template.
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:
layout.erb
<!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:
index.erb
<p>This is the home page</p> <p><a id="link" href="/form">Sign up for our newsletter!</a></p>
form.erb
<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>
thankyou.erb
<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 config.ru
file:
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
s, When
s, and Then
s:
-
Given -
Given
lines describe what pre-condition should exist. -
When -
When
lines describe the actions you take. -
Then -
Then
lines 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 Then
line.
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 Then
steps.
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 Given
and 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 page.has_content? text
.
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 sinatra_steps.rb
:
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)
Again, run 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 protected]" And I click "Sign Up!" Then I should see "Hi there, John Doe. You'll new receive our email newsletter at [email protected]"
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_link
and 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:
Conclusion
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!
Comments