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

How to configure Ubuntu's keyboard to work like a Mac's

Typing accents on a PC is a complicated Alt + three numbered code affair. One feels like a sorcerer casting a spell. "I summon thee accented é! I press the weird magical key Alt, and with 0191 get the flipped question mark!" For a bilingual person this meant that writing on the computer was a start-and-stop process. With Mac's it a whole lot easier, just Alt + e and the letter you wanted for accents and alt + ? for the question mark. No need to leave the keyboard for the number pad and no need to remember arcane number combinations or have a paper cheat sheet next to the keyboard, as I've seen in virtually every secretaries computer in Puerto Rico.

Linux has a interesting approach to foreign language characters: using a compose key. You hit this key which I typically map to Caps Lock and ' and the letter you want and voilá you get the accent. Kinda makes sense: single quotation mark is an accent, double gets you the ümalaut, works pretty well. Except for the ñ, wh…

Fixing Autocomplete in Github's Atom Text Editor for Ruby

I really like Github's Atom Text Editor. I really like that it's multi-platform allowing me to master one set of skills that is transferable to all platforms and all machines. 

On thing that just burns me of the default set-up in Atom is the Autocomplete feature that seems to change my words as a type them. Because Ruby uses the end of line as a terminus for a statement you usually finish a word with pressing the return button and you get really annoying changes to your finished typed word a la MS Word. I find myself yelling "No that's not what I wrote!" at the screen in busy coffee shops.

I disabled autocomplete for a while but it is a very useful function. Then I found out they changed the package that gave the autocomplete to a new one called "Autocomplete Plus" that gives you more options. All that I needed to change to make autocomplete sane again:

1. Open Atom's Preferences
2. Search the bundled packages for "Autocomplete Plus"

3. Go to t…

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 to what …