Skip to main content

Testing with Cucumber, Sinatra and Capybara

Everything you need to know

There are many elements you need to simultaneously learn to do effective testing of your code. Because some of these elements are very simple a lot of explanations just jump over what you need to know and give them up as obvious. Let’s start with a list of the things you need to learn:
  • Gherkin (the language of Cucumber) ——> super easy
  • Capybara (the DSL that controls the browser tests)
  • Rspec (the DSL in which the actual pass/fail tests are written.)
None of these are hard. But having to learn all at the same time can seem daunting. But it’s not! It’s easy peasy but takes time. :-/

It took me three days to get a handle on this. And I hope by reading this you’ll get a handle on it much much quicker.

Let’s start with Cucumber first.
image of a cucumber by Mgmoscatello via Wikimedia commons

Cucumber

Five things you need to know about Cucumber:
  1. Cucumber tests are located on a features folder that have plain text files with a .feature extension and written in Gherkin.
  2. The .feature files contain tests that have a (1.)Feature: heading that explains what feature of the program you’re testing, with a (2.)Senario: tag that follows it and the Scenario uses keywords (3. Given, When, Then, And, But, *) to define the steps you go through testing your scenario. These keywords aren’t important (they can all be substituted by *) they just refer to how they’re defined.
  3. Because when you run your features files with cucumber features/testname.feature the output will give you exactly what you need to put on your step definition file to actually create the tests.
  4. The feature files refer to files in the step_definitions directory. These are plain ruby files that have the same name as the feature files but with _step.rb at the end instead of .feature extension. This is where you can paste the output of the previous command.
  5. The configuration file is in the support folder and called env.rb. This is automatically generated by cucumber-sinatra below.
That’s pretty much it for Cucumber. The step definitions have regex that matches the steps and a block that runs some code that actually creates the test but that’s all Ruby.
Side Note: You can use escaped (w/ a backslash) Markdown inside your Gherkin (feature files).

Example:

Feature: Switch Languages
  As a user of the website I want to be able to switch languages (English and Spanish).

Scenario: Spanish links work
  Given I am on "es/home"
  When I follow "contáctenos" within ".footer"  
  And I should see "Contáctenos:" within ".yielded"

Cucumber-Sinatra

What cucumber-sinatra does is set all the config files for your Sinatra Application to run cucumber with capybara and rspec and creates a sample step file with common step definitions such as When I got to "page-name". These are placed in the file called web_steps in the step-definitions directory inside the features directory.

|-->features  
     -- feature files (.feature)  
     |--> step-definitions directory.  
       -- web_steps.rb  
     |--> support directory  
       -- env.rb  
       -- paths.rb

In order for this to work cucumber-sinatra needs to know the class name of your modular Sinatra App and the path to the ruby file where it’s located.
To generate these files you do:

cucumber-sinatra init  MyAppClassName app/main.rb

Capybara

Capybara is a DSL for interacting with a web application. It’s the successor of a previous tool called ‘WebRat’ (hence the name, a capybara is a large cute rodent).
image of a capybara from wikipedia
Capybara allows you to run your Sinatra application in two ways:
  1. Fast one directly through Rack (which doesn’t run the javascript on your page).
  2. Slower one through a headless browser using Selenium by default.
To run the second one all you need to do is preface your scenario with a @javascript tag.

Example:

@javascript
Scenario: Switch to English Language
  Given I am on "es/home"
  And I should see "Servicios en Línea" within "#prTopBanner"
  When I press "English" within ".footer"
  Then The page redraws in English 
  And I should see "Online Services" within "#prTopBanner"
  And The html lang attribute is not "es"

Almost all the steps above are already generated by cucumber-rails. I use the @javascript tag because the language switching is done by javascript on the page. The only step I had to define was “Then The page redraws in English” & “And the html lang attribute is not ‘es’”. Since the first one of those is mainly rspec I’ll discuss that one later.

Then(/^The html lang attribute is not "(.*?)"$/) do |language_set|
  page.has_no_xpath?('.//html[@lang="#{language_set}"]')
end

This step is defined in Ruby with a regex (regular expression) that feeds whatever is within the ” ” quotes to a block as a variable I called language_set. Then I call on a Capybara Query (page.has_no_xpath?) to check that hat the html language attribute is not set to the variable supplied in quotes.

Capybara provides a rich DSL for interacting with the page elements. I found an excellent cheat sheet here. It’s pretty straight forward and relies on many of the same CSS matching elements as JQuery does. But most tests will require more than just a quick check of something with Capybara, Capybara typically just gets you the elements you need to test, and you test those expectations with rspec.
Side Note: To explore Capybara inside pry (or irb) just requrie 'capybara' and then include Capybara::DSL and you should have access to all the Capybara objects. (try Capybara.pry in pry.)

Rspec

Rspec is another DSL for writing tests. Not just cucumber tests but any kind of test from unit to acceptance. Rspec looks super complicated to me, but that’s mostly because the old syntax (should) tries to be so much like a natural English sentence that it becomes harder to understand. I much prefer the new (expect) syntax. So lets look at an example.

Example:

Then(/^The page redraws in English$/) do
  current_path = URI.parse(current_url).path
  current_path_first_element = current_path.split('/')[1]
  expect(current_path_first_element).to eq('en')
end

This example sets a variable to the path using Capybara’s current_url method. Then creates an expectation of what part of that path should be. I find this new syntax much easier to read since the methods are clearly called. Here is a blog post by one of the Rspec maintainers describing the new syntax.

So, what is this testing exactly, anyway? The way the language feature works on the webpage it uses javascript to change the path appending the language tag to the beginning of the page.
Rspec has mocks (fake objects) and other goodies. But to get started all you need is the simple ‘expect(something).to’ syntax. Most cheat sheets include only the old syntax, here is one for the new one. Please note that not_to and to_not are aliases and you can use whichever you like (I prefer the later).
Side Note: To load Rspec on pry (or irb) just require 'rspec' and include Rspec::Matchers.

BDD, Testing and why do testing?

A key element to Behavior-Driven-Development (BDD), that I recently understood, is that the highest-level tests (the so called acceptance tests), the ones that are done in cucumber, are the ones that provide the most value. I’d seen presentations of BDD and TDD (test-driven-development) and many seemed only useful for expert programmers that knew how to solve the problem. But truly the best value tests give you are: documentation, introspection, and coverage for refactoring.

Yet only the third one requires some extensive testing. The first two can be satisfied to some extent solely with cucumber tests. By just looking at the test you know that the language switching occurs in javascript and does something to the path, specifically to the first element of the path. This is valuable knowledge for developers and maintainers looking at your application code.

The second one, introspection, forces you to think about code that is testable, maintainable and can grow. So even a happy-path test that just test what the application is supposed to do, can be very useful. By writing tests after the application was working, I was able to locate bugs that I had missed simply because the test required me to think in a different way and look at the code form a different perspective. For more on BDD you can look at this youtube video.

Testing Middleware

This setup above works very well for Sinatra based apps, but not for Rack Middleware. I’m still figuring how to effectively do cucumber tests for that.
Update: I figure out how to do it. Open the env.rb file and change this line Capybara.app = MyAppClassName to this long as thing below.

Capybara.app = eval "Rack::Builder.new {( " + File.read(File.dirname(__FILE__) + 
'/../../config.ru') + "\n )}"

— David

Comments

Popular posts from this blog

Building my own home.

I've decided. I want to build my own home. There is something special about building your own things. I built a desk for my tiny room when I first moved to L.A. My room was so small that I had to sit on the bed to use the computer so I build a high desk so I could sit on the bed and work on the computer. My roommate Trentity helped me cut the ply-wood to the right side. I still have that desk. It now sits on the living room covered by a cloth hiding the surplus of costume parts my current roommate Sean uses in his creations. Learning to build and fix things continue. And the feeling of satisfaction from fixing even small things is great. So a few years ago I heard on the NPR program the Story about a couple of educators that moved to a tent in their back-yard so they could rent their house and afford to send their kids to college. They had a special type of tent called a yurt and cooked and showered in an RV they had parked next to it. I thought I could do that. Housing in Lo

Contrasting Styles of Writing: English vs. Spanish

There is interestingly enough a big difference between what's considered good writing in Spanish and English . V.S. Naipul winner of the 2001 Nobel prize for literature publish an article on writing . In it he emphasizes the use of short clear sentences and encourages the lack of adjectives and adverbs. Essentially he pushes the writer to abandon florid language and master spartan communication . This is a desired feature of English prose , where short clipped sentences are the norm and seamlessly flow into a paragraph. In English prose the paragraph is the unit the writer cares about the most. This is not the case in Spanish where whole short stories (I'm thinking this was Gabriel Garcia Marquez but maybe it was Cortázar) are written in one sentence. Something so difficult to do in English that the expert translator could best manage to encapsulate the tale in two sentences. The florid language is what is considered good writing in Spanish but unfortunately this has lead t

My Fake Resume

Inspired by the over aggrandized bio of Joseph Rakofsky I want to write my own. If you don't know who he is; Joseph Rakofsky is a lawyer who earned a mistrial for a criminal client due to his (alleged) incompetence as reported on the Washington Post . There has been quite a few commentaries on his "Streisand-house" approach of suing all the bloggers and even the Washington Post and American Bar Association for reporting his (alleged) ineptitude. ("Streisand-house" is what happened to Barbara Streisand who wanted to have a picture of her mansion removed from the internet and she sued to have it removed. Unfortunately suing requires the filing of public documents with a picture of her house. The lawsuit had the direct opposite effect it intended. Everybody now could see legally, since it was a public document, a picture of her house.) But all that internet gossip aside I'm most impressed by his resume. Here is a quote from the website: Prior to stud