RSpec.configure

You’ve seen how easily you can set configuration options for a particular spec run via the command line. You’ve also seen how to make your favorite options the default using .rspec files.

As convenient as they are, command-line flags are not available for all RSpec options—just the ones you’re likely to change from run to run. For the rest, you’ll need to call RSpec.configure inside one or more Ruby files. You can have multiple configure blocks in your code base; if you do, RSpec will combine the options from all of them.

On a typical project, you’ll put setup in spec/spec_helper.rb and then load this file automatically by adding --require spec_helper to your .rspec file.

Be Careful What You Load from spec_helper.rb

images/aside-icons/info.png

It’s easy for your spec_helper file to get bogged down with code that you don’t need for every spec, turning a spec run that would normally finish in hundreds of milliseconds into a multisecond “I wonder what’s interesting on Twitter?” slog.

You’ll have a much more enjoyable TDD experience if you limit spec_helper to load just the dependencies you always want. If you need a library just for a subset of specs, load it conditionally by doing one of the following:

  • Add a when_first_matching_example_defined hook inside your Rspec.configure block

  • require your library from the top of spec files that need it

You’ve used RSpec.configure several times as you’ve worked through the examples in this book. We’ll do a quick review of the techniques you’ve seen, and in the process we’ll show you a few more options.

Hooks

Hooks allow you to declare chunks of code that run before, after, or around your specs. A hook can run for each :example, once for each :context, or globally for the entire :suite.

We looked at hooks in detail in Hooks. As a reminder, here’s a typical before hook defined in an RSpec.configure block:

 RSpec.configure ​do​ |config|
  config.before(​:example​) ​do
 # ...
 end
 end

We’d like to review one other special-purpose configuration hook that doesn’t fit the typical before/after/around pattern. In Isolating Your Specs Using Database Transactions, you saw a way to run expensive database setup code on demand, the first time it’s needed:

 RSpec.configure ​do​ |config|
  config.when_first_matching_example_defined(​:db​) ​do
  require ​'support/db'
 end
 end

This hook uses metadata (the :db symbol) to perform extra configuration just for the specs that need it.

While config hooks are a great way to reduce duplication and keep your examples focused, there are significant downsides if you overuse them:

  • A slow test suite due to extra logic running for every example
  • Specs that are harder to understand because their logic is hidden in hooks

To avoid these pitfalls while keeping your specs organized, you can use a simpler, more explicit technique: using Ruby modules inside your configure blocks.

Sharing Code Using Modules

Modules are one of Ruby’s main tools for sharing code. You can add all of a module’s methods into a class by calling include or prepend:

 class​ Performer
 include​ Singing ​# won't override Performer methods
  prepend Dancing ​# may override Performer methods
 end

You can even bring methods into an individual object:

 average_person = AveragePerson.new
 average_person.extend Singing

RSpec provides the same kind of interface inside RSpec.configure blocks. By calling include, prepend, or extend on the config object, you can bring extra methods into your examples or groups.

 RSpec.configure ​do​ |config|
 # Brings methods into each example
  config.include ExtraExampleMethods
 
 # Brings methods into each example,
 # overriding methods with the same name
 # (rarely used)
  config.prepend ImportantExampleMethods
 
 # Brings methods into each group (alongside let/describe/etc.)
 # Useful for adding to RSpec's domain-specific language
  config.extend ExtraGroupMethods
 end

Because they work like their Ruby counterparts that you already use, these three config methods are great for sharing Ruby methods across your specs. If you need to share more, though—such as hooks or let definitions—you’ll need to define a shared example group. You can then bring in this shared group automatically inside your configure block:

 RSpec.configure ​do​ |config|
  config.include_context ​'My Shared Group'
 end

Now that we’ve covered sharing code via a configure block, let’s talk about controlling how RSpec runs.

Filtering

Several times throughout this book, you’ve found the need to run just some of the examples in your suite. At various points, you’ve used RSpec’s filtering to run the following subsets of specs:

  • A single example or group by name
  • Only the specs matching a certain piece of metadata, such as :fast
  • Just the examples you’re focusing your attention on
  • Only the examples that failed the last time they ran

Let’s look at how these techniques tie into RSpec’s configuration system. Inside a configure block, you can use the following methods to specify which specs to run:

config.example_status_persistence_file_path = ’spec/examples.txt’

Tells RSpec where to store the passed, failed, or pending status of each example between runs, enabling the --only-failures and --next-failure options.

config.filter_run_excluding :specific_to_some_os

Excludes examples from being run; useful for permanent exclusions based on environmental factors like OS, Ruby version, or an environment variable.

config.filter_run_when_matching :some_metadata

Sets a conditional filter that only applies when there are matching examples; this is how, for instance, RSpec runs just the examples you’ve tagged with the :focus metadata.

Metadata

As we discussed in Chapter 8, Slicing and Dicing Specs with Metadata, RSpec’s metadata system makes it possible for you to categorize your specs in a way that makes sense to you. Metadata is deeply connected to the configuration system. Many of RSpec’s configuration options we’ve discussed accept (or require) a metadata argument, which determines the examples or groups the config option applies to.

You can also set metadata using the configuration system. The following methods let you write metadata on examples or groups:

config.define_derived_metadata(file_path: /unit/) { |meta| meta[:type] = :unit }

Derives one metadata value from another. Here, we tag all the specs in the unit directory with type: :unit. If we had left out the file_path argument, this call would have set metadata for all examples.

config.alias_example_to :alias_for_it, some_metadata: :value

Defines an alternative to the built-in it method that creates an example and attaches metadata. This is how RSpec’s built-in fit method marks examples you want to focus on.

config.alias_example_group_to :alias_for_describe, some_metadata: :value

Like the previous alias, except that it works on example groups instead of individual examples (like RSpec’s fdescribe).

Output Options

RSpec aims to provide actionable output—that is, output that helps you decide what to do next. To that end, it supports a number of options to make the output useful for specific situations:

config.warnings = true

Enables Ruby’s warnings mode, like the rspec --warnings flag we discussed earlier. This helps you catch some mistakes (such as method redefinitions and variable misspellings) but may report tons of extra warnings in third-party code, unless you use something like ruby_warning_filter to cut some of the noise.[65]

config.profile_examples = 2

RSpec will measure how long each spec took and print the given number of slowest examples and groups (two, in this case). This is helpful for keeping your test suite fast.

When an expectation fails, RSpec prints the backtrace showing the chain of method calls all the way from your spec to the lowest-level code. RSpec excludes its own stack frames from this list. You can also exclude other libraries or files from the backtrace:

config.backtrace_exclusion_patterns << /vendor/

Excludes any lines from the backtrace matching the given regular expressions; for example, lines containing the text vendor.

config.filter_gems_from_backtrace :rack, :sinatra

Excludes stack frames from specific libraries; here, we won’t see calls from inside the rack and sinatra gems.

If you ever need more detail, you can get the full backtrace (including stack frames from RSpec and any others you have configured it to ignore) by passing --backtrace on the command line.

Nearly all of RSpec’s output is customizable using a formatter. As we’ve previously discussed, you can specify a formatter on the command line using the --format or -f option. You can also add a formatter in an RSpec.configure block:

 RSpec.configure ​do​ |config|
 # You can use the same formatter names supported by the CLI...
  config.add_formatter ​'documentation'
 # ...or pass _any_ formatter class, including a custom one:
  config.add_formatter Fuubar
 end

This example uses the Fuubar formatter, which is one of the more popular and useful third-party formatters.[66]

As the add_formatter method suggests, you can add multiple formatters, directing each to a different output:

 RSpec.configure ​do​ |config|
  config.add_formatter ​'documentation'​, $stdout
  config.add_formatter ​'html'​, ​'specs.html'
 end

If you don’t call add_formatter or pick a formatter from a command-line option, RSpec will default to the progress formatter. However, you can provide a different default using config.default_formatter:

 RSpec.configure ​do​ |config|
  config.default_formatter = config.files_to_run.one? ? ​'doc'​ : ​'progress'
 end

With this snippet, RSpec will default to the more verbose documentation formatter if you’re running just one spec file, or the progress formatter if you’re running multiple files. Regardless, you can override this default by passing a formatter on the command line.

Library Configuration

The most common way to run RSpec is to use rspec-core to run your specs, rspec-expectations to express expected outcomes, and rspec-mocks to provide test doubles. But you don’t have to use them together. They ship as three separate Ruby gems precisely so that you can swap any of them out. You can even use rspec-mocks or rspec-expectations with another test framework, as we discuss in Using Parts of RSpec With Other Test Frameworks.

Mocks

The config.mock_with option sets which mock object framework RSpec will use. If you want to use Mocha instead of RSpec mocks, you can do so with the following code:[67]

 RSpec.configure ​do​ |config|
  config.mock_with ​:mocha
 end
 
 RSpec.describe ​'config.mock_with :mocha'​ ​do
 it​ ​'allows you to use mocha instead of rspec-mocks'​ ​do
  item = stub(​'Book'​, ​cost: ​17.50)
 
  credit_card = mock(​'CreditCard'​)
  credit_card.expects(​:charge​).with(17.50)
 
  PointOfSale.purchase(item, ​with: ​credit_card)
 end
 end

RSpec also supports the :rr and :flexmock mocking libraries.[68][69]

You can pass a block to mock_with to set options for the mocking library. In the following snippet, we turn on some extra verification in rspec-mocks:

 RSpec.configure ​do​ |config|
  config.mock_with ​:rspec​ ​do​ |mocks|
  mocks.verify_partial_doubles = ​true
  mocks.verify_doubled_constant_names = ​true
 end
 end

We’ll talk more about configuring your test doubles in Using Partial Doubles Effectively. In case you’re curious, here’s what these two options do:

mocks.verify_partial_doubles = true

Verifies that each partial double—a normal object that has been partially modified with test double behavior—conforms to the object’s original interface.

mocks.verify_doubled_constant_names = true

When creating a verifying double using a string such as "SomeClassName", RSpec will verify that SomeClassName actually exists.

The full configuration docs for rspec-mocks are available online.[70]

Expectations

Just as mock_with lets you configure an alternative to rspec-mocks, expect_with lets you choose a different assertion framework instead of rspec-expectations:

 require ​'wrong'
 
 RSpec.configure ​do​ |config|
  config.expect_with ​:minitest​, ​:rspec​, Wrong
 end
 
 RSpec.describe ​'Using different assertion/expectation libraries'​ ​do
 let​(​:result​) { 2 + 2 }
 
 it​ ​'works with minitest assertions'​ ​do
  assert_equal 4, result
 end
 
 it​ ​'works with rspec expectations'​ ​do
 expect​(result).to eq 4
 end
 
 it​ ​'works with wrong'​ ​do
 # "Where 2 and 2 always makes 5..."
  assert { result == 5 }
 end
 end

Here, we are using three different libraries: rspec-expectations, Minitest’s assertions, and a great little library called Wrong.[71][72] (You will not normally need to use multiple assertion libraries; we are just demonstrating some different options.)

You can give expect_with a well-known library name—:rspec, :minitest, or :test_unit—or you can pass in a Ruby module containing the assertion methods you want to use. If you’re writing your own module, your methods should signal a failure by raising an exception.

Other Useful Options

Before we wrap up this chapter, let’s take a look at a few final options that don’t fit into the categories we’ve described.

Zero Monkey-Patching Mode

The first option we’d like to highlight here is disable_monkey_patching!:

 RSpec.configure ​do​ |config|
  config.disable_monkey_patching!
 end

This flag disables RSpec’s original syntax:

 # Old syntax
 
 describe​ SwissArmyKnife ​do​ ​# bare `describe` method
 it​ ​'is useful'​ ​do
  knife.should be_useful ​# `should` expectation
 end
 end

…in favor of the style in RSpec 3:

 # New syntax
 
 RSpec.describe SwissArmyKnife ​do​ ​# `describe` called on the `RSpec` module
 it​ ​'is useful'​ ​do
 expect​(knife).to be_useful ​# `expect()`-style expectation
 end
 end

The old syntax relied heavily on monkey-patching core Ruby objects. The new zero-monkey-patching mode does not. The result is fewer gotchas and edge cases in your specs. Moreover, your code will continue to work with future versions of RSpec. For more information about this mode, see the RSpec blog post that introduced it.[73]

Random Order

As we discussed in Testing the Invalid Case, we recommend you configure RSpec to run your specs in random order:

 RSpec.configure ​do​ |config|
  config.order = ​:random
 end

Running your specs in random order helps surface ordering dependencies between your examples. You’ll be more likely to discover these problems when they first appear, and you’ll be able to fix the bug while the code is still fresh in your mind.

Adding Your Own Settings

All of the settings we’ve seen so far ship with RSpec. But you’re not limited to those. RSpec provides an API for adding new settings, which you can then use in your own libraries that extend RSpec’s behavior.

For example, suppose you’re writing an RSpec plugin and accompanying web service to help developers track long-term trends about their test suites. After each spec run, your plugin would report runtimes and pass/fail status to the service.

Your users will need some way to configure your plugin to use their assigned API keys for the web service. In your library, you’d call add_setting inside an RSpec.configure block, passing it the name of the setting you’re creating:

 RSpec.configure ​do​ |config|
  config.add_setting ​:spec_history_api_key
 end

Once a developer has installed your plugin, he or she can set it up to use the API key like so:

 RSpec.configure ​do​ |config|
  config.spec_history_api_key = ​'a762bc901fga4b185b'
 end

Even if you’re just writing a library for your own projects, adding these kinds of configuration options can make it easier to use.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset