giant robots smashing into other giant robots

Written by thoughtbot

dancroak

New Factory Girl definition syntax

factory_girl and factory_girl_rails release candidates were released this week thanks to Joe and Josh.

The big change is some great-looking new syntax. Check it out:

Old:

Factory.sequence :email do |n|
  "email#{n}@example.com"
end

Factory.define :user do |factory|
  factory.name  { 'Ron Burgundy' }
  factory.email { Factory.next(:email) }
end

Factory.define :admin, parent: :user do |factory|
  factory.admin { true }
end

Factory.define :dog do |factory|
  factory.name { 'Baxter' }
  factory.association(:owner, factory: :user)
end

New:

FactoryGirl.define do
  sequence :email do |n|
    "email#{n}@example.com"
  end

  factory :user, aliases: [:owner] do
    name 'Ron Burgundy'
    email

    factory :admin do
      admin true
    end
  end

  factory :dog do
    name 'Baxter'
    owner
  end
end

It reminds me of the difference between Rails 2.x routes and Rails 3.x routes.

Read the GETTING STARTED document in the repo, then try it in your app:

gem 'factory_girl_rails', '1.1.rc1'

Written by .

dancroak

AuthorizedController

We use a few Rails engines such as High Voltage and Clearance which allow you to override default behaviors of controllers by subclassing them like:

class SessionsController < Clearance::SessionsController
  layout 'public-facing'
end

Meanwhile, we often work on apps which require most of the controllers to be authorized. If we wanted to have a home page that uses High Voltage, one way we might do it is:

class ApplicationController < ActionController::Base
  include Clearance::Authentication # or other authentication mechanism
  before_filter :authorize
end

class PagesController < HighVoltage::PagesController
  skip_before_filter :authorize
end

MyApp::Application.routes.draw do
  # all the authorized routes
  resources :pages
end

It’s annoying to have a new route plus a new controller and to define and override a before_filter. Meanwhile, the authorization logic is “hidden” in ApplicationController.

Another way is:

class ApplicationController < ActionController::Base
  include Clearance::Authentication
end

class CommentsController < ApplicationController
  before_filter :authorize
end

For each controller, you call the before_filter. It’s declarative in the sense that when you read the CommentsController class, you can see it’s protected by authorization. The downsides are:

  • you have to remember to write it for each controller
  • you have to write some custom authorization specs for the controller

Another way is:

class ApplicationController < ActionController::Base
  include Clearance::Authentication
end

class AuthorizedController < ApplicationController
  before_filter :authorize
end

Now, we subclass from AuthorizedController for only those controllers that need the functionality.

class CommentsController < AuthorizedController
  # ...
end

Again, it’s declarative, we can read the class and know it’s authorized. Again, it requires we remember, which means we might forget.

I’d argue that if we TDD the controller, we shouldn’t forget.

The advantage of subclassing from the intermediate AuthorizedController over the “before_filter :authorize in each controller” style means we can feel confident with a spec like:

describe CommentsController do
  it { should be_kind_of(AuthorizedController) }
end

That assumes we have a spec somewhere like:

class MocksController < AuthorizedController
  def show
  end
end

describe MocksController do
  before do
    MyApp::Application.routes.draw do
      resource :mock, :only => [:show]
      match 'sign_in' => 'sessions#new', :as => :sign_in
    end
  end

  after do
    Rails.application.reload_routes!
  end

  context "a visitor" do
    it "is denied access to blank slate" do
      get :show
      should redirect_to(sign_in_url)
    end
  end
end

If this style gains a little support, we could put AuthorizedController and this spec into Clearance itself so it wouldn’t need to be hanging around in each of our app’s codebases.

I feel like it’s the right amount of work, with the right amount of test coverage, without extraneous subclassing and routes.

Which approach do you prefer? Something different?

cpytel

thoughtbot and the Holy Grail

Update: Our quest for full-stack testing continued with writing the capybara-webkit driver. We also wrote about capybara-webkit more recently.


Stop! Who would cross the Bridge of Death must answer me these questions three, ‘ere the other side he see.

Ask me the questions, bridge-keeper. I’m not afraid.

What is your name?

My name is cpytel of thoughtbot.

What is your favorite color?

Red.

What is your quest?

To seek a reliable, robust way of integration testing our entire application to prevent against regressions and to build better systems.

With apologies to Monty Pyton, we’ve been on this quest for several years now. After many false starts and tribulations I feel we’ve reached an important plateau in this quest, and I feel its prudent to take a step back, look at where we’ve been, where we are now, and pass on some of this information for the benefit of all.

Selenium

Throughout this quest, Selenium has been the siren song that continually calls out to us. Unfortunately, in practice we’ve been unable to get Selenium to run reliably for real applications, on both developers machines and on the continuous integration server.

This failure with Selenium has caused us to search for alternative solutions.

Holy Grail

The first promising solution was the aptly named, Holy Grail. This library uses Harmony, which in turn wraps Johnson, env.js and Envjs to execute browser-less, console-based, javascript and DOM code right from within your Rails test suite. Our own Jason Morrison then wrote cucumber-holygrail to allow Cucumber to drive the Holy Grail integration tests.

Unfortunately, this solution wasn’t really designed to accomplish what we wanted; Holy Grail lets you run javascript on a single page and routes xhr requests to a controller in a functional test, and therefore it didn’t work like we expected: we wanted to drive a virtual browser session from action to action that supported javascript and ui interactions.

Envjs

From there, we discovered capybara-envjs. Capybara is a replacement for Webrat (the driver underneath Cucumber) which has a more flexible driver subsystem. capybara env-js provides more of what we needed, it had the goal of being a virtual browser, like we wanted. In order to make it work like we wanted, we needed to provide a number of fixes which we encapsulated into the capybara-envjs-fixes gem (as a holding place while we waited for pull requests to be merged in).

Capybara and capybara-envjs, and capybara-envjs-fixes were (and mostly still are) a deadly combination. We’re actively using it on projects still. However, the more we used it, the more we started to discover cracks in the foundation.

  • It doesn’t work with all javascript (for example, we had issues on sites that still use Prototype)
  • It doesn’t support jQuery live
  • It is sometimes very inconsistent with how real-world browsers behave, or how things should properly work, forcing us to write our code like we wouldn’t normally write it

The three above points, particularly the lack of support for jQuery live (which is used by more and more of the internal of jQuery, jQuery mobile, and even the new Rails unobtrusive javascript) caused us to continue to search for a better solution.

Akephalos

Thankfully, we then found Akephalos. Akephalos provides a Capybara driver that allows you to run your cucumber integration tests in the headless browser HtmlUnit. HtmlUnit is a “GUI-Less browser for Java programs”. It models HTML documents and provides an API that allows you to invoke pages, fill out forms, click links, etc… just like you do in your “normal” browser. With our fork of Akephalos to resolve a couple of issues that we ran into along the way, we were up and running with very reliable, headless browser tests.

HtmlUnit is written in Java, and Akephalos uses jruby-jars to start up and interact with the HtmlUnit browser. It has fairly good JavaScript support (it was able to deal with everything we were able to throw at it, including jQuery 1.4.2 and 1.4.3, jQuery Mobile, and jQuery live).

Note: We also tried culerity which is a cucumber driver that works with Celerity, which also is underpinned by HtmlUnit, but we couldn’t get it to really work well.

One of the great things about HtmlUnit and Akephalos is that it uses technology which has been existence in a while, is actively developed, and well documented. We were able to look at the code and understand what was going on and contributate changes if necessary much more easily than we were able to do with any of the previous attempts.

If you want to get started with Akephalos, I recommend that you use our fork on github until the changes are pulled in. We’ve used Akephalos on both Rails 2 and 3.

The relevant part of our Gemfile looks like this:

group :test, :cucumber do
  gem 'akephalos', :git => 'git://github.com/thoughtbot/akephalos.git'
  gem "cucumber-rails"
  gem "capybara"
  gem "database_cleaner"
  gem "treetop"
  gem "launchy"
end

Then, to a features/support/akephalos.rb add:

require 'akephalos'

You can then tag any scenarios you want to run with Akephalos with the @akephalos tag. If you want to be able to tag your scenarios with @javascript and have it execute in Akephalos (the default javascript driver in Capybara is Selenium) you’ll want to add the following additional line to your features/support/akephalos.rb file.

Capybara.javascript_driver = :Akephalos

There are a couple of important gotchas right now that will effect you, particularly if you have an existing application.

There is a known bug in HtmlUnit where any jQuery live events bound to the click event stop any jQuery live events bound to submit defined after them. In particular, this means that if you’re using Rails 3 unobtrusive jQuery form helpers, you’ll want to edit your rails.js file and move the definition of the follow event:

$('form[data-remote]').live('submit'

Up above any jQuery live click bindings.

Additionally, ajax requests execute asynchronously, just like in a real browser. In order for the tests to not get out of sequence and fail, ajax requests need to be executed synchronously instead. You can do this by adding the following code after your inclusion of jQuery in only your test or cucumber environments:

    $.ajaxSetup({ async: false });  

Akephalos doesn’t have the ability to set cookies, or access the session. These are capabilities in other drivers, and so you may find that your cucumber steps are doing this, and they’ll need to be rewritten. For example, the built in clearance step definitions read the cookies in order to expose a current_user helper for step definitions to use, and deleted cookies in order to force a sign out. There is newly committed support for reading cookies in Akephalos, but I couldn’t get it to work, and I figured as a matter of practice it’d be better not to rely on it anyway.

Have we reached the end of our quest?

We’re not the only ones on this quest, I’m sure, and I encourage those of you who are to take a look at Akephalos and give it a try, I don’t think you’ll be disappointed.

Unfortunately, I don’t think the quest is over.  But Akephalos represents a significant step forward in a reliable headless browser that can allow us to fully integration test our applications. There are probably otther options available to us to try, and I expect there will be more as time goes on. For example, we’ve recently started to use evergreen to do javascript unit testing right alongside the normal tests.

We’ll continue to report back on what we find and consider the current state-of-the-art. Let us know if you’ve found and love something we’ve missed here.

dancroak

Test-driven Rails helper

This is how I unit tested a view helper to format a date range in a view.

I wrote the interface for the view helper I wanted, but which did not exist yet:

= format_date_range(course.date_range)

The outer integration tests (not shown) failed with NoMethoError: format_date_range.

I generated a new helper file:

script/generate helper date_time

It produced:

# app/helpers/date_time_helper.rb
module DateTimeHelper
end

# test/unit/helpers/date_time_helper_test.rb
require 'test_helper'

class DateTimeHelperTest < ActionView::TestCase
end

I wrote the unit test:

should 'format date range on same day' do
  eight_oclock = DateTime.new(2009, 10, 12, 8)
  nine_oclock = DateTime.new(2009, 10, 12, 9)
  date_range = eight_oclock..nine_oclock
  expected = 'October 12, 2009'

  assert_equal expected, format_date_range(date_range)
end

This style is flat test structure, intention-revealing temporary variables, respect an 80-character line limit.

I made it pass:

def format_date_range(date_range)
  first = date_range.first
  last  = date_range.last

  if same_day?(first, last)
    "#{first.to_s(:month_day)}, #{first.year}"
  end
end

private

def same_day?(date_one, date_two)
  date_one.day == date_two.day
end

Like the test, I used intention-revealing temporary variables and a private method for a more expressive question in the conditional.

I wrote another test case:

should 'format date range on different days of same month' do
  monday = DateTime.new(2009, 10, 12)
  tuesday = DateTime.new(2009, 10, 13)
  date_range = monday..tuesday
  expected = 'October 12-13, 2009'

  assert_equal expected, format_date_range(date_range)
end

I made it pass:

def format_date_range(date_range)
  # ...
  elsif same_month?(first, last)
    "#{first.to_s(:month_day)}-#{last.day}, #{last.year}"
  end
end

private

def same_month?(date_one, date_two)
  date_one.month == date_two.month
end

Rails date and time formatting is done in locales:

en:
  date:
    formats:
      long: ! '%B %d, %Y'
      month_day: ! '%B %d'
      short: ! '%b %d'

I wrote another test case:

should 'format date range on days of different months' do
  october = DateTime.new(2009, 10, 31)
  november = DateTime.new(2009, 11, 1)
  date_range = october..november
  expected = 'October 31-November 01, 2009'

  assert_equal expected, format_date_range(date_range)
end

I made it pass:

else
  "#{first.to_s(:month_day)}-#{last.to_s(:month_day)}, #{last.year}"
end

I wanted to remove the leading zero sucks. We could use `%e instead of %d, which replaces the leading zero with a space:

expected = "October 31-November  1, 2009"

That makes the test look a little ugly but since the output will be HTML, the extra space is fine.

Make it pass:

month_day: ! '%B %e'

Written by .

dancroak

Testing ActiveRecord named scopes

Everyone was psyched when Nick Kallen’s has_finder plugin was added to Rails as named_scope. They’re powerful, particularly when chaining.

One disadvantage is that they are so easy to create, people want their tests to be equally concise, which is often impossible.

Two related “testing named scopes” questions have come up recently on the thoughtbot training mailing list and the shoulda mailing list.

Movie recommendations: InternetFlicks

We’re going to create a movie recommendation system called InternetFlicks.

script/generate model Movie ranking:integer in_stock:boolean
script/generate model Viewing user:belongs_to movie:belongs_to

The user:belongs_to syntax is from Blitz. We’ll assume a User model is already created, perhaps using Clearance.

Outside-In

Say we’re working on the recommendations page for the signed in user. RESTfully, this might be:

/recommendations

We can use a simple authorization strategy:

class RecommendationsController < ApplicationController
  before_filter :authenticate
  def index
    @movies = Movie.recommended_for(current_user)
  end
end

I’m skipping a few steps here, but our goal is to determine what interface the model needs to expose.

Also note that we’re intentionally crossing resources (“recommendations”) in the RESTful sense with a model of a different name (“Movie”). This is something we’ve started to stress in training as many students have thought REST means they need to match controller names to models (probably because they’ve seen scaffold generation).

TDD will guide you to better habits

Use Role Suggesting Name for the test name, variable names, and method under test to describe the behavior of Movie.recommended_for.

class MovieTest < ActiveSupport::TestCase
  should "recommend 2 highest ranked, in stock movies unwatched by user" do
    user    = Factory(:user)
    top_out = Factory(:movie, :ranking => 100, :in_stock => false)
    top_in  = Factory(:movie, :ranking => 95,  :in_stock => true)
    next_in = Factory(:movie, :ranking => 90,  :in_stock => true)
    watched = Factory(:movie, :ranking => 100, :in_stock => true)
    Factory(:viewing, :user => user, :movie => watched)

    assert_equal [top_in, next_in], Movie.recommended_for(user, 2)
  end
end

This is a state-based test. We create a user and a few movies, have him or her watch a movie, exercise the method, and verify the results match the test name.

Skipping ahead again, say we’ve gone through a few red, green, refactor cycles, taking into account some edge cases, and the implementation now looks like this:

class Movie < ActiveRecord::Base
  def self.recommended_for(user, limit = 10)
    highest_ranked.in_stock.unwatched(user).limited(limit)
  end

  private

  named_scope :highest_ranked, :order => "ranking desc"
  named_scope :in_stock, :conditions => { :in_stock => true }
  named_scope :unwatched, lambda { |user|
    { :joins      => "left outer join viewings
                                   on viewings.movie_id = movies.id
                                  and viewings.user_id  = #{user.id}
                      left outer join users
                                   on users.id = viewings.id",
      :conditions => "users.id is null" }
  }
end

Cool, named_scopes helped us make the recommended_for method expressive. If the specification for it changes, it will be a joy to change.

No tests for the private methods

We’ll test those private methods like this.

If an object outside of Movie needs highest_ranked or one of the other private methods, we’ll write a unit test for it, watch it error, and bring it into a public scope piece by piece.

Discarded option: should_have_named_scope

About a year ago, should_have_named_scope was introduced to Shoulda.

It was later deprecated. We never felt comfortable using it. While there’s something to be said for quick Shoulda one-liners, I don’t think there’s much of an argument for should_have_named_scope except for simple scopes that are responsible for order or limit.

Since those are usually taken care of by something like utility_scopes or Pacecar (and thus do not need to be unit tested) OR are often relegated to private status, that doesn’t leave a niche for should_have_named_scope.

If you’re still a should_have_named_scope fan, please write a test for unwatched using it. I’ll be surprised if it stands up to peer review.

Discarded option: stub_chain

Another option I’ve been using this summer, but have recently rejected is stub_chain.

module StubChainMocha
  module Object
    def stub_chain(*methods)
      while methods.length > 1 do
        stubs(methods.shift).returns(self)
      end
      stubs(methods.shift)
    end
  end
end
Object.send(:include, StubChainMocha::Object) 

This approach unit tests each of the named scopes and then stub_chains the class method:

should "find 10 highest ranked movies in stock that you have not seen" do
  user = Factory(:user)
  Movie.stub_chain(:highest_ranked, :in_stock, :unwatched, :limited)

  Movie.recommended_for(user)

  assert_received(Movie, :highest_ranked)
  assert_received(Movie, :in_stock)
  assert_received(Movie, :unwatched) { |expect| expect.with(user) }
  assert_received(Movie, :limited)   { |expect| expect.with(10) }
end 

This is easy to understand, but very tied to implementation, does not test the “integration” of the scopes (dangerous in many situations when the resulting SQL is not combined as expected), and stubs out methods on the same object (usually a smell that something needs to be refactored).

My suggested approach may often be more lines of code, but will better describe the behavior.

No chains in controllers

Some of us have started to use a rule of thumb of “no chains in controllers”. This makes the testing decisions easier, and makes the model’s public interfaces and public/private interface distinction cleaner. The end result is usually a class method that wraps (potentially private) named scopes.