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 Dan Croak.
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:
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?
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.
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.
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.
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.
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.
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.
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.
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 Dan Croak.
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.
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.
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).
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.
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.
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.
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.
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.