Advertisement

Ruby for Newbies: Testing Web Apps with Capybara and Cucumber

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

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?

Press HD for the clearest picture.
Subscribe to our YouTube and Blip.tv channels to watch more screencasts.

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"

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&#39;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: Givens, Whens, and Thens:

  • 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 "john@doe.com"
    And I click "Sign Up!"
    Then I should see "Hi there, John Doe. You&#39;ll new receive our email newsletter at john@doe.com"

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!

Advertisement