Skip to content

Instantly share code, notes, and snippets.

@myronmarston
Last active April 6, 2022 19:47
Show Gist options
  • Select an option

  • Save myronmarston/9c21b85c784871161d36 to your computer and use it in GitHub Desktop.

Select an option

Save myronmarston/9c21b85c784871161d36 to your computer and use it in GitHub Desktop.

Revisions

  1. myronmarston revised this gist Jan 23, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion response.md
    Original file line number Diff line number Diff line change
    @@ -21,7 +21,7 @@ to require from `~/.rspec`) put:

    ``` ruby
    RSpec.configure do |c|
    # Each of these can be one of the ANSI color codes integers or one
    # Each of these can be one of the ANSI color code integers or one
    # of [:black, :white, :red, :green, :yellow, :blue, :magenta, :cyan].
    # The values I've assigned here are the defaults so you'll want to change them.

  2. myronmarston revised this gist Jan 23, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion response.md
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@ apply to any project on your machine. In this file put something like:
    --require "~/configure_rspec_colors.rb"
    ```

    And then in "~/configure_rspec_colors.rb" (or whatever file you decide
    And then in `~/configure_rspec_colors.rb` (or whatever file you decide
    to require from `~/.rspec`) put:

    ``` ruby
  3. myronmarston revised this gist Jan 23, 2015. 1 changed file with 45 additions and 0 deletions.
    45 changes: 45 additions & 0 deletions response.md
    Original file line number Diff line number Diff line change
    @@ -129,6 +129,39 @@ or `context`. (I'd also point out that we do not consider `set_it_up`,
    `children` and `register` to be part of our public API and this may
    break in future versions...)

    > Can I put it in a module?
    You can indeed. This works:

    ```
    module MyHelpers
    def helper
    11
    end
    end
    describe "something" do
    include MyHelpers
    it "works" do
    expect(10).to eq(helper)
    end
    it "really works" do
    expect(11).to eq(helper)
    end
    end
    ```

    If you wanted the module globally included in all example groups, you
    can call `include` on config:

    ``` ruby
    RSpec.configure do |config|
    config.include MyHelpers
    end
    ```

    > But if that’s the case, why not just use Ruby classes?
    There are a few reasons for this:
    @@ -259,3 +292,15 @@ this feature is, we also have some shortcuts: `fit` is an alias for `it`
    with `:focus`, `fdescribe` is an alias for `describe` with `:focus` and
    `fcontext` is an alias for `context` with `:focus` -- so just prefix any
    of these with the letter `f` to temporarily make RSpec run only them.

    > Now, do I care which one you use? No. As long as you test your code, I
    > am happy. A professional developer should be able to work in either
    > one of these because they essentially do the same thing: test your
    > code.
    <3 <3 I completely agree with this. I'd go so far as to say that as lead
    RSpec maintainer, I'm extremely grateful that RSpec's not the only
    testing library around. It's good for the Ruby community that we have
    multiple well-maintained libraries for testing.

    Thanks again, @tenderlove!
  4. myronmarston created this gist Jan 23, 2015.
    261 changes: 261 additions & 0 deletions response.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,261 @@
    > I highly suspect that the RSpec core team all use black backgrounds in
    > their terminals because sometimes the colors aren’t so nice on my
    > white terminal
    I certainly use a black background. I'm not sure about the other RSpec
    core folks. Regardless, if there are some color changes we can make that
    would make output look good on a larger variety of backgrounds, we'll
    certainly consider that (do you have some suggested changes?).
    In the meantime, the colors are configurable, so you can change the colors
    to fit your preferences on your machine. First, create a file at
    `~/.rspec`. If this file exists, RSpec will use the command line options
    configured in this file, allowing you to set personal preferences that
    apply to any project on your machine. In this file put something like:

    ```
    --require "~/configure_rspec_colors.rb"
    ```

    And then in "~/configure_rspec_colors.rb" (or whatever file you decide
    to require from `~/.rspec`) put:

    ``` ruby
    RSpec.configure do |c|
    # Each of these can be one of the ANSI color codes integers or one
    # of [:black, :white, :red, :green, :yellow, :blue, :magenta, :cyan].
    # The values I've assigned here are the defaults so you'll want to change them.

    # Color config is available in RSpec 2.13+. Since this file will
    # always be loaded by RSpec and you may have projects on earlier
    # versions, it's a good idea to only do this on versions that support it.
    if c.respond_to?(:default_color=)
    c.default_color = :white
    c.detail_color = :cyan
    c.failure_color = :red
    c.fixed_color = :blue
    c.pending_color = :yellow
    c.success_color = :green
    end
    end
    ```

    > But where is the `helper` method defined? What’s it’s visibility? Can I
    > put it in a module? Can I use inheritance? Who can call it? Can I call
    > `super` from the extracted method? If so, where does `super` go? These are
    > not the types of questions I want to be pondering when I have 3000
    > test failures and really long spec files to read through. My mind
    > spins off in to thoughts like “I wonder how RSpec works?”, and “I
    > wonder what my cat is doing?”
    >
    > From what I can gather, calling `describe` in RSpec essentially defines
    > a class.
    This is indeed what it does (plus some other stuff that I'll get into in a bit).
    I think it'll help you understand by showing an example:

    ``` ruby
    describe "Using an array as a stack" do
    def build_stack
    []
    end

    def stack
    @stack ||= build_stack
    end

    it 'is initially empty' do
    expect(stack).to be_empty
    end

    context "after an item has been pushed" do
    def build_stack
    super.push :item
    end

    it 'allows the pushed item to be popped' do
    expect(stack.pop).to eq(:item)
    end
    end
    end
    ```

    This example is almost exactly (sans some details such as RSpec
    assigning the classes to different constants) doing this:

    ``` ruby
    class UsingAnArrayAsAStack < RSpec::Core::ExampleGroup
    set_it_up "Using an array as a stack"

    def build_stack
    []
    end

    def stack
    @stack ||= build_stack
    end

    it 'is initially empty' do
    expect(stack).to be_empty
    end

    class AfterAnIteHasBeenPushed < self
    set_it_up "after an item has been pushed"

    def build_stack
    super.push :item
    end

    it 'allows the pushed item to be popped' do
    expect(stack.pop).to eq(:item)
    end
    end

    children << AfterAnIteHasBeenPushed
    end

    RSpec.world.register UsingAnArrayAsAStack
    ```

    As you can see, RSpec is in fact creating classes, just like you
    guessed, and it uses `class_exec` to evaluate the `describe` block.
    Individual examples are evaluated in the context of an instance of
    the class (like minitest!).
    Nested contexts are simply subclasses, which means they inherit
    all helper methods just like you would expect, and you can use `super`
    as you would expect. You can also see that RSpec does some extra stuff
    (`set_it_up` plus registering the classes via `children <<` and
    `RSpec.world.register` for the top-level one) when you call `describe`
    or `context`. (I'd also point out that we do not consider `set_it_up`,
    `children` and `register` to be part of our public API and this may
    break in future versions...)

    > But if that’s the case, why not just use Ruby classes?
    There are a few reasons for this:

    * In its original formulation (as per [Dave
    Astel's](http://blog.daveastels.com.s3-website-us-west-2.amazonaws.com/2014/09/29/a-new-look-at-test-driven-development.html)
    and [Dan North's](http://dannorth.net/introducing-bdd/) articles introducing BDD),
    one of the things BDD was about was using a new vocabulary to discuss testing, that, in their
    experience, led you in a better direction when learning TDD.
    `describe` and `it` come from that heritage. I think that they were on
    to something, but I also think that the alternate vocabulary is most
    useful for people learning TDD (as they discuss in their articles).
    For someone who has developed code using TDD for years, the alternate
    vocabulary probably doesn't matter much, and may even be a hindrance for
    someone very used to the xUnit style.
    * `describe` allows you to pass arbitrary English descriptions. Ruby
    classes have many restrictions on what is allowed in their names. Many
    people (including myself!) find it very useful to be able to use
    arbitrary English descriptions. I often include a rationale
    in my descriptions so that if a test ever fails, the developer is
    given an explanation for _why_ the behavior expressed in the test was
    desirable at the time it was written (which can help them decide if
    they still need to maintain that behavior). For example, here's one
    test description from a project at work:

    ``` ruby
    it 'ignores subsecond differences in timestamps since MySQL and redis store them at different granularities' do
    # ...
    end
    ```

    * The fact that `describe` is a method that accepts arguments means we
    can provide APIs for extensions to be able to get the "class being
    tested". This is a more explicit alternative to what
    `ActionController::TestCase` does: rather than trying to infer the
    class-under-test from the test class name, the user gives it to us as
    an explicit argument. Explicit argument means less potential confusion
    when the user misspells it (they'll get an immediate NameError from
    ruby!), and the user doesn't have to know anything about how
    inferrence works.
    * `describe` also supports RSpec's metadata system, where example groups
    (test classes) and examples (individual test methods) have attached
    metadata that can be used to slice and dice your suite in many ways.
    For example, you can tag an example group as `:slow` (`describe
    MyClass, :slow do`) and exclude groups with that tag (`rspec --tag "~slow"`).
    rspec-rails uses `:type` metadata to know which Rails testing APIs to
    make available to which example groups. You can tag an example group
    as `:pending` to indicate that it should not yet pass, and RSpec will
    run it, expecting it to fail, and notify you if it now passes. These
    are all features that would be harder to support if you couldn't pass
    additional arguments when defining your test class.

    > You’ll see “hi!” printed 3 times, once for each it, and “hello!”
    > printed once. For the nested context, I would like to print “hello!”
    > before it prints “hi!”. In a normal inheritance situation, I would
    > change the call to super. I am not sure how to do what I just
    > described without learning a new thing
    Honestly, I never thought about the fact that the `def setup` style of
    hooks allows you to control the order like this! This is indeed
    something that doesn't have a simple solution in RSpec. I'll think more
    about if there's anything we can do to improve it.

    Overall, when I run into this type of situation -- where I've got a code
    in a `before` hook, but an individual test needs to run something else
    first -- I treat it as a code smell. It suggests that the code doesn't
    really belong in a `before` hook since it's not actually something that
    we always want to run first. Usually I'll pull the code in the hook out
    into a helper method and then explicitly call it from the places where it
    needs to happen.

    All that said, there is a way to accomplish what you are trying to do,
    but it's pretty obtuse:

    ``` ruby
    describe "something" do
    def say_hi
    puts "hi!"
    end

    before do |ex|
    say_hi unless ex.metadata[:skip_hi]
    end

    # ...

    context "another thing", :skip_hi do
    before do
    puts "hello!"
    say_hi
    end

    # ...
    end
    end
    ```

    This uses the metadata system (which I mentioned earlier) to skip the
    logic in the before hook, and then in the nested group we can call it at
    an appropriate time. As I said, that's far less elegant than using
    `super` like you mentioned, and I don't recommend doing this, but it
    could be a useful temporary step during a larger refactoring.

    > Another thing that bugs me is that if I do try to refactor my RSpec
    > tests, and I change any lines, then the line that I copy and pasted to
    > run just the one test won’t work anymore. I have to figure out the new
    > line. I can use the name of the test by specifying the -e flag on RSpec,
    > but then I lose the “copy, paste, run” feature that I love so much.
    My suggestion is to use RSpec's focus-filtering feature. The
    `spec_helper.rb` file generated by `rspec --init` has a couple lines of
    config that you'll need for this:

    ``` ruby
    RSpec.configure do |config|
    config.filter_run :focus
    config.run_all_when_everything_filtered = true
    end
    ```

    With that configured, you can (temporarily) add `:focus` metadata to any
    example or example group, and RSpec will run just those examples or
    example groups. If nothing has `:focus`, then everything will be run
    (that's what the last line of config is for). With the `:focus` metadata
    it doesn't matter if your specs move around to other lines. Given that
    you'll only want to temporarily change the metadata, and how useful
    this feature is, we also have some shortcuts: `fit` is an alias for `it`
    with `:focus`, `fdescribe` is an alias for `describe` with `:focus` and
    `fcontext` is an alias for `context` with `:focus` -- so just prefix any
    of these with the letter `f` to temporarily make RSpec run only them.