Thanks for reading Testing Rails! Here's some quick answers to some of your questions. I'll clean this up later and add a section in the book.
I primarily use context
for communication around conditionals. Or in other
words, to give extra context around what the it
will be testing. Since I
don't use let
or before
blocks, I don't use context
blocks to group
common setup functionality. So, they don't provide any additional
functionality to the running test. The benefit is only for the programmer to be
able to understand what the test is for, both when reading the test in the file
itself, or when reading the output when running the specs with documentation
format (i.e. rspec spec --format documentation
). A rule of thumb is that any
time I start to write "if", "when", or "with some state" in an it
block,
that may be a good candidate for pulling out a context
block.
Here's an example of how I'm using context
blocks in my current app. Note that
I've removed some syntax and code so I can focus on the test blocks themselves.
describe PoolQuery, "#pools"
context "with a valid date"
it "returns that day's pools, sorted chronologically"
context "with an invalid date"
it "returns today's pools, sorted chronologically"
context "with no date"
it "returns today's pools, sorted chronologically"
Each of the above tests would do their own setup, potentially extracting duplicated logic into a function that clearly communicates what is being set up.
Here's a few more uses of context blocks:
describe "GET /api/v1/pools"
it "returns available pools"
it "doesn't return participants who have canceled the booking"
it "filters pools by date range"
context "when passed a `from` but no `to`"
it "defaults `to` to the end of day, derived from `from`"
describe "pools/_pool.html.erb"
it "displays the pool"
context "with participants"
it "doesn't display a delete link"
context "with no participants"
it "displays a delete link"
I would consider nesting context
blocks a strong antipattern. First of all, it
makes tests difficult to follow when you've indented more than a few levels
deep. Second, every time you use context
it points to a conditional in your
code. If you are nesting context
s, the code you are testing likely has too
much logic, or you are testing that code at too low a level. If you find
yourself needing to nest context
s, consider whether you could extract a
function or class and test the conditional there. If you're testing something
high level, you may not need to test the conditional if you've already tested it
at a lower level.
Unrelated to context
, but on the subject of nesting, note that I don't nest
describe
in my model specs. Instead of a top level describe
with the model
name, and nested describe
s with the method name:
describe SomeModel
describe ".some_class_method"
describe "#some_instance_method"
I'll keep these on at single level and have multiple top level blocks to avoid unnecessary nesting:
describe SomeModel, ".some_class_method"
describe SomeModel, "#some_instance_method"
I try not to nest any further than a single describe
, context
and it
.
The only exceptions I would say for using let
and friends are the occasional
before
and after
block you might define in a spec_helper
to clean up data
or reset state (i.e. with DatabaseCleaner). For example, I have an
after(:suite)
that cleans up images from Paperclip.
config.after(:suite) do
FileUtils.rm_rf(Dir["#{Rails.root}/public/test_files/"])
end
Other than that, I would not introduce these hooks into a test suite. There's
nothing that you can do with them that you can't also do with a plain old Ruby
method. While let
may save you a little time to begin with and a few lines of
code, that can't compete with the explicitness you get from inlined methods.