Skip to content

Instantly share code, notes, and snippets.

@jsteiner
Last active October 28, 2016 19:15
Show Gist options
  • Save jsteiner/d5d1f7e7e5f5331dfa2caa5f79653b4a to your computer and use it in GitHub Desktop.
Save jsteiner/d5d1f7e7e5f5331dfa2caa5f79653b4a to your computer and use it in GitHub Desktop.
On context

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.

context

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"

Nesting context

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 contexts, 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 contexts, 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 describes 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.

let and friends

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment