A couple perspectives on testing common scenarios
One Perspective
Suppose we have a Rails ActiveRecord model for a book:
class Book < ActiveRecord::Base
validates_presence_of :title
end
Some people suggest testing the validation of the title by just checking to see if either A) validates_presence_of(:title)
was called or B) whatever the side effect of validates_presence_of(:title)
is present in the object (e.g. creating a method, adding an object to an array of validators, etc.) I believe their objective is to both DRY up their tests as well as not worrying about testing validates_presence_of
, since that should already be tested by the framework.
I tend to disagree with this approach, and would much rather we test the behavior of a book. Let’s instantiate a book without a title, and assert that it is in fact invalid.
# for test/unit
def test_books_should_require_titles
book = Book.new(:title => nil)
assert !book.valid?
assert book.errors.on(:title)
end
# for rspec
describe Book do
it "should require titles" do
book = Book.new(:title => nil)
book.should have(1).error_on(:title)
end
end
While I do think the other approach is better than not testing at all, I believe testing the behavior in this case has a higher expected value. In other words, I feel that divorcing the test from the particular implementation will make our test more robust than the former. The prime example I can see for this scenario is that testing the behavior will encompass both using validates_presence_of
and any other form (in case our implementation is a bit more complex).
I can respect trying to DRY up the tests and not wanting to duplicate it for every attribute. For that I would suggest creating a helper that tests a series of attributes for you. Something like:
describe Book do
should_require(:title, :author, :published_at, :copyrighted_at)
end
Backpedaling a little
I actually use the first approach often when I’m testing my controllers. Usually when I want to focus on a testing an action in my controller, and I want to test it in isolation. However I do want to assert that a filter is assigned to that controller.
Suppose we had a controller for library on a college campus that only allowed Students and Faculty in:
class LibraryController < ApplicationController
before_filter :requires_students_or_faculty
def checkout
... implementation of the action ...
end
end
First I would assert that the library checks people, but I don’t want to worry about it when I want to test checkout
# Staying with RSpec
describe LibraryController, "filters" do
should_have_before_filter(:requires_students_or_faculty)
end
describe LibraryController, "something to do with checkout" do
stub_all_filters!
it "should redirect the patron outside after checking out a book" do
... implementation of the test ...
end
end
Minor Note - stub_all_filters!
runs through all filters and stubs them out so they all return true
, making them irrelevant.
I’m not doing this in order to remove the responsibility of testing the the filter itself. I whole heartedly believe it should be tested too, but separating the tests can help avoid clunky tests with a lot of noise.
Another way to look at it, is to consider this perspective of “behavior”. While I do agree that the filters contribute to the overall behavior of a controller, when I want to test a specific behavior (in this case, checkout
), I don’t want my tests to have to satisfy preconditions requires_students_or_faculty
. For example, checking if a user is a student or faculty could be complex, and there’s no reason to care about that when testing checkout