Tag Archives: tdd

Testing tips with Capybara on Rails

Downtime, layoffs, security breaches, and bugs are among the worst things that technology teams have to deal with. Surprisingly, bugs are the most manageable. To help make bugs more trivial, here are a few application testing techinques when working with Rails.

Any time you deploy new code, you need to have confidence the new code works as advertised and that old code hasn’t broken down. There’s no way to have 100% confidence, but having good code coverage is a start.

Rails provides many ways to test, but in my opinion the two most important ways are unit tests and integration tests. Unit tests examine your low-level model code, and makes sure core functionality doesn’t break. In any modern web app worth its salt, you will need full-stack integration tests. How do you know that an Ajax action actually replaced the element you thought it would?  Functional controller testing is decoupled, but assumes form parameters are sent through in a certain way, but what if there is a bug in your html? You need to be able to reproduce user behavior exactly as a user behaves, with no assumptions. This couples things, but that’s ok. Better to be coupled and catch a bug, than not catch it all.

We use capybara and capybara-webkit for headless browser testing. I have a love/hate relationship with capybara. On one hand it provides the best full stack testing interface for a Rails app I’ve ever seen and has saved me countless hours of debugging.  On the other hand, I”ve hit so many weird synchronization issues (due to the asynchronous nature of ajax) with capybara and capybara-webkit and spent countless hours pulling my hair out.  See this discussion on Capybara’s google group (https://groups.google.com/d/topic/ruby-capybara/sjgRGiNcL7g/discussion) and this one on capybara-webkit’s (https://groups.google.com/d/topic/capybara-webkit/i51R5I4sMCI/discussion)

They are not without faults, but the gains are so worth it when you see your test suite go green prior to a deployment.  Also, most of Capybara’s synchronization’s issues have been solved in their latest version, I think > 2.0.  Shout out to Jonas Niklas for his great work on this project.

I do have a small bone to pick though. Prior to v2 of Capybara, it had this nifty helper method called #wait_until which I used heavily to wait until an element appears.  In v2 this was removed, see: http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara.  Jonas says that there shouldn’t be any synchronization issues any more and that if there are its really just a matter of how you are testing.  Hmm… test suites should get out of your way, no? Not tell you how to write your tests?  I don’t know, perhaps he’s right, but I didn’t look kindly on rewriting our massive test suite.  Luckily, Jonas is so awesome he provided this workaround that plugged and played right into our suite: https://gist.github.com/jnicklas/d8da686061f0a59ffdf7

def wait_until
  require “timeout”
  Timeout.timeout(Capybara.default_wait_time) do
    sleep(0.1) until value = yield
Awesome.  Also, here’s a nifty capybara pattern I just came up with and posted on my personal blog, http://p373.net/2013/02/22/capybara-custom-matcher-alternative/
If you want to add functionality to a Capybara::Session, or “page”, where you need to check multiple things, and want to do it in a DRY and reusable fashion, check this snippet:

#Let's say you want to check super awesomeness on the page
#which involves checking multiple things, like the current path,
#the page has some content, and a particular css selector is present
#Like so:
page.should be_super_awesome

#in spec_helper.rb
Capybara::Session.send(:include, SuperAwesomeHelper::Session)

#The the implementation:
module SuperAwesomeHelper
  module Session
    def super_awesome?
      errors = false
      errors ||= "Wrong path" unless current_path == super_awesome_path
      errors ||= "Missing content: You are awesome" unless !errors and has_content?("You are awesome")
      errors ||= "Missing selector: #awesome-div" unless !errors and has_selector?("#awesome-div")
      People.all.each{|p| errors ||= "missing person #{p.name}" unless !errors and has_selector?(".person-#{p.name}")}
      !errors or raise Capybara::ExpectationNotMet, errors
      return true

Check the p373 blog post for all the gritty details.